refactored repositories and DB queries (#5026)
* removed unused teams from export order * added new paginator for query instead of querybuilder * added field hydrate enums * hide PARTIAL deprecation * never log deprecations in production * replaced InvoiceLoader with native Doctrine feature * prevent excessive permission queries * support loading customers of team * improved findByIds * internalized API * fix null string deprecations
This commit is contained in:
251
composer.lock
generated
251
composer.lock
generated
@@ -265,23 +265,23 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dasprid/enum",
|
"name": "dasprid/enum",
|
||||||
"version": "1.0.5",
|
"version": "1.0.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/DASPRiD/Enum.git",
|
"url": "https://github.com/DASPRiD/Enum.git",
|
||||||
"reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016"
|
"reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016",
|
"url": "https://api.github.com/repos/DASPRiD/Enum/zipball/8dfd07c6d2cf31c8da90c53b83c026c7696dda90",
|
||||||
"reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016",
|
"reference": "8dfd07c6d2cf31c8da90c53b83c026c7696dda90",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.1 <9.0"
|
"php": ">=7.1 <9.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^7 | ^8 | ^9",
|
"phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11",
|
||||||
"squizlabs/php_codesniffer": "*"
|
"squizlabs/php_codesniffer": "*"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
@@ -309,9 +309,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/DASPRiD/Enum/issues",
|
"issues": "https://github.com/DASPRiD/Enum/issues",
|
||||||
"source": "https://github.com/DASPRiD/Enum/tree/1.0.5"
|
"source": "https://github.com/DASPRiD/Enum/tree/1.0.6"
|
||||||
},
|
},
|
||||||
"time": "2023-08-25T16:18:39+00:00"
|
"time": "2024-08-09T14:30:48+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/cache",
|
"name": "doctrine/cache",
|
||||||
@@ -585,16 +585,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/dbal",
|
"name": "doctrine/dbal",
|
||||||
"version": "3.8.6",
|
"version": "3.9.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/doctrine/dbal.git",
|
"url": "https://github.com/doctrine/dbal.git",
|
||||||
"reference": "b7411825cf7efb7e51f9791dea19d86e43b399a1"
|
"reference": "d8f68ea6cc00912e5313237130b8c8decf4d28c6"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/b7411825cf7efb7e51f9791dea19d86e43b399a1",
|
"url": "https://api.github.com/repos/doctrine/dbal/zipball/d8f68ea6cc00912e5313237130b8c8decf4d28c6",
|
||||||
"reference": "b7411825cf7efb7e51f9791dea19d86e43b399a1",
|
"reference": "d8f68ea6cc00912e5313237130b8c8decf4d28c6",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -610,12 +610,12 @@
|
|||||||
"doctrine/coding-standard": "12.0.0",
|
"doctrine/coding-standard": "12.0.0",
|
||||||
"fig/log-test": "^1",
|
"fig/log-test": "^1",
|
||||||
"jetbrains/phpstorm-stubs": "2023.1",
|
"jetbrains/phpstorm-stubs": "2023.1",
|
||||||
"phpstan/phpstan": "1.11.5",
|
"phpstan/phpstan": "1.11.7",
|
||||||
"phpstan/phpstan-strict-rules": "^1.6",
|
"phpstan/phpstan-strict-rules": "^1.6",
|
||||||
"phpunit/phpunit": "9.6.19",
|
"phpunit/phpunit": "9.6.20",
|
||||||
"psalm/plugin-phpunit": "0.18.4",
|
"psalm/plugin-phpunit": "0.18.4",
|
||||||
"slevomat/coding-standard": "8.13.1",
|
"slevomat/coding-standard": "8.13.1",
|
||||||
"squizlabs/php_codesniffer": "3.10.1",
|
"squizlabs/php_codesniffer": "3.10.2",
|
||||||
"symfony/cache": "^5.4|^6.0|^7.0",
|
"symfony/cache": "^5.4|^6.0|^7.0",
|
||||||
"symfony/console": "^4.4|^5.4|^6.0|^7.0",
|
"symfony/console": "^4.4|^5.4|^6.0|^7.0",
|
||||||
"vimeo/psalm": "4.30.0"
|
"vimeo/psalm": "4.30.0"
|
||||||
@@ -678,7 +678,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/doctrine/dbal/issues",
|
"issues": "https://github.com/doctrine/dbal/issues",
|
||||||
"source": "https://github.com/doctrine/dbal/tree/3.8.6"
|
"source": "https://github.com/doctrine/dbal/tree/3.9.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -694,7 +694,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-06-19T10:38:17+00:00"
|
"time": "2024-08-15T07:34:42+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/deprecations",
|
"name": "doctrine/deprecations",
|
||||||
@@ -1388,16 +1388,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/orm",
|
"name": "doctrine/orm",
|
||||||
"version": "2.19.6",
|
"version": "2.19.7",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/doctrine/orm.git",
|
"url": "https://github.com/doctrine/orm.git",
|
||||||
"reference": "c1bb2ccf4b19c845f91ff7c4c01dc7cbba7f4073"
|
"reference": "168ac31084226f94d42e7461a40ff5607a56bd35"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/doctrine/orm/zipball/c1bb2ccf4b19c845f91ff7c4c01dc7cbba7f4073",
|
"url": "https://api.github.com/repos/doctrine/orm/zipball/168ac31084226f94d42e7461a40ff5607a56bd35",
|
||||||
"reference": "c1bb2ccf4b19c845f91ff7c4c01dc7cbba7f4073",
|
"reference": "168ac31084226f94d42e7461a40ff5607a56bd35",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1483,9 +1483,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/doctrine/orm/issues",
|
"issues": "https://github.com/doctrine/orm/issues",
|
||||||
"source": "https://github.com/doctrine/orm/tree/2.19.6"
|
"source": "https://github.com/doctrine/orm/tree/2.19.7"
|
||||||
},
|
},
|
||||||
"time": "2024-06-26T17:24:40+00:00"
|
"time": "2024-08-23T06:54:57+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/persistence",
|
"name": "doctrine/persistence",
|
||||||
@@ -1586,16 +1586,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/sql-formatter",
|
"name": "doctrine/sql-formatter",
|
||||||
"version": "1.4.0",
|
"version": "1.4.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/doctrine/sql-formatter.git",
|
"url": "https://github.com/doctrine/sql-formatter.git",
|
||||||
"reference": "d1ac84aef745c69ea034929eb6d65a6908b675cc"
|
"reference": "7f83911cc5eba870de7ebb11283972483f7e2891"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/d1ac84aef745c69ea034929eb6d65a6908b675cc",
|
"url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/7f83911cc5eba870de7ebb11283972483f7e2891",
|
||||||
"reference": "d1ac84aef745c69ea034929eb6d65a6908b675cc",
|
"reference": "7f83911cc5eba870de7ebb11283972483f7e2891",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1635,9 +1635,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/doctrine/sql-formatter/issues",
|
"issues": "https://github.com/doctrine/sql-formatter/issues",
|
||||||
"source": "https://github.com/doctrine/sql-formatter/tree/1.4.0"
|
"source": "https://github.com/doctrine/sql-formatter/tree/1.4.1"
|
||||||
},
|
},
|
||||||
"time": "2024-05-08T08:12:09+00:00"
|
"time": "2024-08-05T20:32:22+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "egulias/email-validator",
|
"name": "egulias/email-validator",
|
||||||
@@ -3115,16 +3115,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nelmio/api-doc-bundle",
|
"name": "nelmio/api-doc-bundle",
|
||||||
"version": "v4.29.0",
|
"version": "v4.29.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/nelmio/NelmioApiDocBundle.git",
|
"url": "https://github.com/nelmio/NelmioApiDocBundle.git",
|
||||||
"reference": "c9523906023e61351f03dbab8d077173f5ec4883"
|
"reference": "61a3f8bb95111fade6eace55c071c43da0cc75d9"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/c9523906023e61351f03dbab8d077173f5ec4883",
|
"url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/61a3f8bb95111fade6eace55c071c43da0cc75d9",
|
||||||
"reference": "c9523906023e61351f03dbab8d077173f5ec4883",
|
"reference": "61a3f8bb95111fade6eace55c071c43da0cc75d9",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -3225,7 +3225,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/nelmio/NelmioApiDocBundle/issues",
|
"issues": "https://github.com/nelmio/NelmioApiDocBundle/issues",
|
||||||
"source": "https://github.com/nelmio/NelmioApiDocBundle/tree/v4.29.0"
|
"source": "https://github.com/nelmio/NelmioApiDocBundle/tree/v4.29.3"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3233,7 +3233,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-07-22T08:40:00+00:00"
|
"time": "2024-08-17T13:03:07+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nelmio/cors-bundle",
|
"name": "nelmio/cors-bundle",
|
||||||
@@ -4432,16 +4432,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/log",
|
"name": "psr/log",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/php-fig/log.git",
|
"url": "https://github.com/php-fig/log.git",
|
||||||
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
|
"reference": "79dff0b268932c640297f5208d6298f71855c03e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
|
"url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e",
|
||||||
"reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
|
"reference": "79dff0b268932c640297f5208d6298f71855c03e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -4476,9 +4476,9 @@
|
|||||||
"psr-3"
|
"psr-3"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/php-fig/log/tree/3.0.0"
|
"source": "https://github.com/php-fig/log/tree/3.0.1"
|
||||||
},
|
},
|
||||||
"time": "2021-07-14T16:46:02+00:00"
|
"time": "2024-08-21T13:31:24+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/simple-cache",
|
"name": "psr/simple-cache",
|
||||||
@@ -9720,16 +9720,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "twig/cssinliner-extra",
|
"name": "twig/cssinliner-extra",
|
||||||
"version": "v3.10.0",
|
"version": "v3.11.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/twigphp/cssinliner-extra.git",
|
"url": "https://github.com/twigphp/cssinliner-extra.git",
|
||||||
"reference": "10e88e9a887b646c58e3d670383208f15295dd22"
|
"reference": "7312a0275812b86918febb4b7a67d0cb084c5d02"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/10e88e9a887b646c58e3d670383208f15295dd22",
|
"url": "https://api.github.com/repos/twigphp/cssinliner-extra/zipball/7312a0275812b86918febb4b7a67d0cb084c5d02",
|
||||||
"reference": "10e88e9a887b646c58e3d670383208f15295dd22",
|
"reference": "7312a0275812b86918febb4b7a67d0cb084c5d02",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -9773,7 +9773,7 @@
|
|||||||
"twig"
|
"twig"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/twigphp/cssinliner-extra/tree/v3.10.0"
|
"source": "https://github.com/twigphp/cssinliner-extra/tree/v3.11.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -9785,20 +9785,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-05-11T07:35:57+00:00"
|
"time": "2024-06-21T06:22:31+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "twig/extra-bundle",
|
"name": "twig/extra-bundle",
|
||||||
"version": "v3.10.0",
|
"version": "v3.11.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/twigphp/twig-extra-bundle.git",
|
"url": "https://github.com/twigphp/twig-extra-bundle.git",
|
||||||
"reference": "cdc6e23aeb7f4953c1039568c3439aab60c56454"
|
"reference": "bf8a304eac15838d7724fdf64c345bdefbb75f03"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/cdc6e23aeb7f4953c1039568c3439aab60c56454",
|
"url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/bf8a304eac15838d7724fdf64c345bdefbb75f03",
|
||||||
"reference": "cdc6e23aeb7f4953c1039568c3439aab60c56454",
|
"reference": "bf8a304eac15838d7724fdf64c345bdefbb75f03",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -9847,7 +9847,7 @@
|
|||||||
"twig"
|
"twig"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.10.0"
|
"source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.11.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -9859,20 +9859,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-05-11T07:35:57+00:00"
|
"time": "2024-06-21T06:25:01+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "twig/inky-extra",
|
"name": "twig/inky-extra",
|
||||||
"version": "v3.10.0",
|
"version": "v3.11.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/twigphp/inky-extra.git",
|
"url": "https://github.com/twigphp/inky-extra.git",
|
||||||
"reference": "adfcc3b2becc09e909d30b813cde17351ac82958"
|
"reference": "f0e6fce7eeab518b5b18b476bc9a5f19a3359354"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/twigphp/inky-extra/zipball/adfcc3b2becc09e909d30b813cde17351ac82958",
|
"url": "https://api.github.com/repos/twigphp/inky-extra/zipball/f0e6fce7eeab518b5b18b476bc9a5f19a3359354",
|
||||||
"reference": "adfcc3b2becc09e909d30b813cde17351ac82958",
|
"reference": "f0e6fce7eeab518b5b18b476bc9a5f19a3359354",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -9917,7 +9917,7 @@
|
|||||||
"twig"
|
"twig"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/twigphp/inky-extra/tree/v3.10.0"
|
"source": "https://github.com/twigphp/inky-extra/tree/v3.11.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -9929,20 +9929,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-05-11T07:35:57+00:00"
|
"time": "2024-06-21T06:22:31+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "twig/intl-extra",
|
"name": "twig/intl-extra",
|
||||||
"version": "v3.10.0",
|
"version": "v3.11.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/twigphp/intl-extra.git",
|
"url": "https://github.com/twigphp/intl-extra.git",
|
||||||
"reference": "693f6beb8ca91fc6323e01b3addf983812f65c93"
|
"reference": "e9cadd61342e71e45b2f4f0558122433fd7e4566"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/twigphp/intl-extra/zipball/693f6beb8ca91fc6323e01b3addf983812f65c93",
|
"url": "https://api.github.com/repos/twigphp/intl-extra/zipball/e9cadd61342e71e45b2f4f0558122433fd7e4566",
|
||||||
"reference": "693f6beb8ca91fc6323e01b3addf983812f65c93",
|
"reference": "e9cadd61342e71e45b2f4f0558122433fd7e4566",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -9981,7 +9981,7 @@
|
|||||||
"twig"
|
"twig"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/twigphp/intl-extra/tree/v3.10.0"
|
"source": "https://github.com/twigphp/intl-extra/tree/v3.11.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -9993,20 +9993,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-05-11T07:35:57+00:00"
|
"time": "2024-06-21T06:25:01+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "twig/string-extra",
|
"name": "twig/string-extra",
|
||||||
"version": "v3.10.0",
|
"version": "v3.11.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/twigphp/string-extra.git",
|
"url": "https://github.com/twigphp/string-extra.git",
|
||||||
"reference": "cd76ed8ae081bcd4fddf549e92e20c5df76c358a"
|
"reference": "d25c61baf38705a72ebb5a92d2e9ecb7c473b8ac"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/twigphp/string-extra/zipball/cd76ed8ae081bcd4fddf549e92e20c5df76c358a",
|
"url": "https://api.github.com/repos/twigphp/string-extra/zipball/d25c61baf38705a72ebb5a92d2e9ecb7c473b8ac",
|
||||||
"reference": "cd76ed8ae081bcd4fddf549e92e20c5df76c358a",
|
"reference": "d25c61baf38705a72ebb5a92d2e9ecb7c473b8ac",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -10048,7 +10048,7 @@
|
|||||||
"unicode"
|
"unicode"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/twigphp/string-extra/tree/v3.10.0"
|
"source": "https://github.com/twigphp/string-extra/tree/v3.11.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -10060,20 +10060,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-05-11T07:35:57+00:00"
|
"time": "2024-08-07T17:34:09+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "twig/twig",
|
"name": "twig/twig",
|
||||||
"version": "v3.10.3",
|
"version": "v3.11.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/twigphp/Twig.git",
|
"url": "https://github.com/twigphp/Twig.git",
|
||||||
"reference": "67f29781ffafa520b0bbfbd8384674b42db04572"
|
"reference": "e80fb8ebba85c7341a97a9ebf825d7fd4b77708d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/67f29781ffafa520b0bbfbd8384674b42db04572",
|
"url": "https://api.github.com/repos/twigphp/Twig/zipball/e80fb8ebba85c7341a97a9ebf825d7fd4b77708d",
|
||||||
"reference": "67f29781ffafa520b0bbfbd8384674b42db04572",
|
"reference": "e80fb8ebba85c7341a97a9ebf825d7fd4b77708d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -10081,7 +10081,8 @@
|
|||||||
"symfony/deprecation-contracts": "^2.5|^3",
|
"symfony/deprecation-contracts": "^2.5|^3",
|
||||||
"symfony/polyfill-ctype": "^1.8",
|
"symfony/polyfill-ctype": "^1.8",
|
||||||
"symfony/polyfill-mbstring": "^1.3",
|
"symfony/polyfill-mbstring": "^1.3",
|
||||||
"symfony/polyfill-php80": "^1.22"
|
"symfony/polyfill-php80": "^1.22",
|
||||||
|
"symfony/polyfill-php81": "^1.29"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"psr/container": "^1.0|^2.0",
|
"psr/container": "^1.0|^2.0",
|
||||||
@@ -10127,7 +10128,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/twigphp/Twig/issues",
|
"issues": "https://github.com/twigphp/Twig/issues",
|
||||||
"source": "https://github.com/twigphp/Twig/tree/v3.10.3"
|
"source": "https://github.com/twigphp/Twig/tree/v3.11.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -10139,7 +10140,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-05-16T10:04:27+00:00"
|
"time": "2024-08-08T16:15:16+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "webmozart/assert",
|
"name": "webmozart/assert",
|
||||||
@@ -10447,26 +10448,26 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/pcre",
|
"name": "composer/pcre",
|
||||||
"version": "3.2.0",
|
"version": "3.3.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/composer/pcre.git",
|
"url": "https://github.com/composer/pcre.git",
|
||||||
"reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90"
|
"reference": "1637e067347a0c40bbb1e3cd786b20dcab556a81"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/composer/pcre/zipball/ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
|
"url": "https://api.github.com/repos/composer/pcre/zipball/1637e067347a0c40bbb1e3cd786b20dcab556a81",
|
||||||
"reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
|
"reference": "1637e067347a0c40bbb1e3cd786b20dcab556a81",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.4 || ^8.0"
|
"php": "^7.4 || ^8.0"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"phpstan/phpstan": "<1.11.8"
|
"phpstan/phpstan": "<1.11.10"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "^1.11.8",
|
"phpstan/phpstan": "^1.11.10",
|
||||||
"phpstan/phpstan-strict-rules": "^1.1",
|
"phpstan/phpstan-strict-rules": "^1.1",
|
||||||
"phpunit/phpunit": "^8 || ^9"
|
"phpunit/phpunit": "^8 || ^9"
|
||||||
},
|
},
|
||||||
@@ -10506,7 +10507,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/composer/pcre/issues",
|
"issues": "https://github.com/composer/pcre/issues",
|
||||||
"source": "https://github.com/composer/pcre/tree/3.2.0"
|
"source": "https://github.com/composer/pcre/tree/3.3.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -10522,7 +10523,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-07-25T09:36:02+00:00"
|
"time": "2024-08-19T19:43:53+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/xdebug-handler",
|
"name": "composer/xdebug-handler",
|
||||||
@@ -11001,16 +11002,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "friendsofphp/php-cs-fixer",
|
"name": "friendsofphp/php-cs-fixer",
|
||||||
"version": "v3.61.1",
|
"version": "v3.62.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||||
"reference": "94a87189f55814e6cabca2d9a33b06de384a2ab8"
|
"reference": "627692f794d35c43483f34b01d94740df2a73507"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/94a87189f55814e6cabca2d9a33b06de384a2ab8",
|
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/627692f794d35c43483f34b01d94740df2a73507",
|
||||||
"reference": "94a87189f55814e6cabca2d9a33b06de384a2ab8",
|
"reference": "627692f794d35c43483f34b01d94740df2a73507",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -11092,7 +11093,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
|
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
|
||||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.61.1"
|
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.62.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -11100,7 +11101,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-07-31T14:33:15+00:00"
|
"time": "2024-08-07T17:03:09+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "masterminds/html5",
|
"name": "masterminds/html5",
|
||||||
@@ -11347,16 +11348,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan",
|
"name": "phpstan/phpstan",
|
||||||
"version": "1.11.9",
|
"version": "1.11.11",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan.git",
|
"url": "https://github.com/phpstan/phpstan.git",
|
||||||
"reference": "e370bcddadaede0c1716338b262346f40d296f82"
|
"reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/e370bcddadaede0c1716338b262346f40d296f82",
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/707c2aed5d8d0075666e673a5e71440c1d01a5a3",
|
||||||
"reference": "e370bcddadaede0c1716338b262346f40d296f82",
|
"reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -11401,7 +11402,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-08-01T16:25:18+00:00"
|
"time": "2024-08-19T14:37:29+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan-deprecation-rules",
|
"name": "phpstan/phpstan-deprecation-rules",
|
||||||
@@ -11452,16 +11453,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan-doctrine",
|
"name": "phpstan/phpstan-doctrine",
|
||||||
"version": "1.4.8",
|
"version": "1.5.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan-doctrine.git",
|
"url": "https://github.com/phpstan/phpstan-doctrine.git",
|
||||||
"reference": "fa497c5cf8a3f9cd3db8cb4033daf5244793d3e1"
|
"reference": "4d17bed8a33aa8220c1f2a21a6b14fcdb0e5b02c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/fa497c5cf8a3f9cd3db8cb4033daf5244793d3e1",
|
"url": "https://api.github.com/repos/phpstan/phpstan-doctrine/zipball/4d17bed8a33aa8220c1f2a21a6b14fcdb0e5b02c",
|
||||||
"reference": "fa497c5cf8a3f9cd3db8cb4033daf5244793d3e1",
|
"reference": "4d17bed8a33aa8220c1f2a21a6b14fcdb0e5b02c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -11518,9 +11519,9 @@
|
|||||||
"description": "Doctrine extensions for PHPStan",
|
"description": "Doctrine extensions for PHPStan",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpstan-doctrine/issues",
|
"issues": "https://github.com/phpstan/phpstan-doctrine/issues",
|
||||||
"source": "https://github.com/phpstan/phpstan-doctrine/tree/1.4.8"
|
"source": "https://github.com/phpstan/phpstan-doctrine/tree/1.5.2"
|
||||||
},
|
},
|
||||||
"time": "2024-07-16T11:31:01+00:00"
|
"time": "2024-08-23T11:07:15+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan-phpunit",
|
"name": "phpstan/phpstan-phpunit",
|
||||||
@@ -11625,16 +11626,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan-symfony",
|
"name": "phpstan/phpstan-symfony",
|
||||||
"version": "1.4.6",
|
"version": "1.4.8",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan-symfony.git",
|
"url": "https://github.com/phpstan/phpstan-symfony.git",
|
||||||
"reference": "e909a075d69e0d4db262ac3407350ae2c6b6ab5f"
|
"reference": "14eec8c011b856eee4d744a2a3f709db1e1858bd"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/e909a075d69e0d4db262ac3407350ae2c6b6ab5f",
|
"url": "https://api.github.com/repos/phpstan/phpstan-symfony/zipball/14eec8c011b856eee4d744a2a3f709db1e1858bd",
|
||||||
"reference": "e909a075d69e0d4db262ac3407350ae2c6b6ab5f",
|
"reference": "14eec8c011b856eee4d744a2a3f709db1e1858bd",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -11691,41 +11692,41 @@
|
|||||||
"description": "Symfony Framework extensions and rules for PHPStan",
|
"description": "Symfony Framework extensions and rules for PHPStan",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
|
"issues": "https://github.com/phpstan/phpstan-symfony/issues",
|
||||||
"source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.6"
|
"source": "https://github.com/phpstan/phpstan-symfony/tree/1.4.8"
|
||||||
},
|
},
|
||||||
"time": "2024-07-16T11:48:54+00:00"
|
"time": "2024-08-13T19:43:40+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
"version": "9.2.31",
|
"version": "9.2.32",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||||
"reference": "48c34b5d8d983006bd2adc2d0de92963b9155965"
|
"reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965",
|
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/85402a822d1ecf1db1096959413d35e1c37cf1a5",
|
||||||
"reference": "48c34b5d8d983006bd2adc2d0de92963b9155965",
|
"reference": "85402a822d1ecf1db1096959413d35e1c37cf1a5",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"ext-libxml": "*",
|
"ext-libxml": "*",
|
||||||
"ext-xmlwriter": "*",
|
"ext-xmlwriter": "*",
|
||||||
"nikic/php-parser": "^4.18 || ^5.0",
|
"nikic/php-parser": "^4.19.1 || ^5.1.0",
|
||||||
"php": ">=7.3",
|
"php": ">=7.3",
|
||||||
"phpunit/php-file-iterator": "^3.0.3",
|
"phpunit/php-file-iterator": "^3.0.6",
|
||||||
"phpunit/php-text-template": "^2.0.2",
|
"phpunit/php-text-template": "^2.0.4",
|
||||||
"sebastian/code-unit-reverse-lookup": "^2.0.2",
|
"sebastian/code-unit-reverse-lookup": "^2.0.3",
|
||||||
"sebastian/complexity": "^2.0",
|
"sebastian/complexity": "^2.0.3",
|
||||||
"sebastian/environment": "^5.1.2",
|
"sebastian/environment": "^5.1.5",
|
||||||
"sebastian/lines-of-code": "^1.0.3",
|
"sebastian/lines-of-code": "^1.0.4",
|
||||||
"sebastian/version": "^3.0.1",
|
"sebastian/version": "^3.0.2",
|
||||||
"theseer/tokenizer": "^1.2.0"
|
"theseer/tokenizer": "^1.2.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^9.3"
|
"phpunit/phpunit": "^9.6"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-pcov": "PHP extension that provides line coverage",
|
"ext-pcov": "PHP extension that provides line coverage",
|
||||||
@@ -11734,7 +11735,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "9.2-dev"
|
"dev-main": "9.2.x-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -11763,7 +11764,7 @@
|
|||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||||
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
||||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31"
|
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.32"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -11771,7 +11772,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-03-02T06:37:42+00:00"
|
"time": "2024-08-22T04:23:01+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-file-iterator",
|
"name": "phpunit/php-file-iterator",
|
||||||
|
|||||||
@@ -19,12 +19,6 @@ when@prod:
|
|||||||
type: console
|
type: console
|
||||||
process_psr_3_messages: false
|
process_psr_3_messages: false
|
||||||
channels: ["!event", "!doctrine", "!deprecation"]
|
channels: ["!event", "!doctrine", "!deprecation"]
|
||||||
# TODO reactivate once the many Doctrine deprecations are fixed
|
|
||||||
# deprecation:
|
|
||||||
# type: stream
|
|
||||||
# channels: ["deprecation"]
|
|
||||||
# path: "%kernel.logs_dir%/deprecations.log"
|
|
||||||
# formatter: monolog.formatter.deprecation
|
|
||||||
|
|
||||||
when@dev:
|
when@dev:
|
||||||
monolog:
|
monolog:
|
||||||
|
|||||||
355
phpstan.neon
355
phpstan.neon
@@ -283,11 +283,6 @@ parameters:
|
|||||||
count: 2
|
count: 2
|
||||||
path: src/Command/ExportCreateCommand.php
|
path: src/Command/ExportCreateCommand.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$customerIDs of method App\\\\Repository\\\\CustomerRepository\\:\\:findByIds\\(\\) expects array\\<int\\>, mixed given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Command/ExportCreateCommand.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#1 \\$datetime of method App\\\\Timesheet\\\\DateTimeFactory\\:\\:createDateTime\\(\\) expects string, mixed given\\.$#"
|
message: "#^Parameter \\#1 \\$datetime of method App\\\\Timesheet\\\\DateTimeFactory\\:\\:createDateTime\\(\\) expects string, mixed given\\.$#"
|
||||||
count: 2
|
count: 2
|
||||||
@@ -308,11 +303,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Command/ExportCreateCommand.php
|
path: src/Command/ExportCreateCommand.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$projectIds of method App\\\\Repository\\\\ProjectRepository\\:\\:findByIds\\(\\) expects array\\<int\\>, mixed given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Command/ExportCreateCommand.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#1 \\$string of function rtrim expects string, mixed given\\.$#"
|
message: "#^Parameter \\#1 \\$string of function rtrim expects string, mixed given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -323,26 +313,11 @@ parameters:
|
|||||||
count: 2
|
count: 2
|
||||||
path: src/Command/ExportCreateCommand.php
|
path: src/Command/ExportCreateCommand.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$teamIds of method App\\\\Repository\\\\TeamRepository\\:\\:findByIds\\(\\) expects array\\<int\\>, mixed given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Command/ExportCreateCommand.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#1 \\$timezone of class DateTimeZone constructor expects string, mixed given\\.$#"
|
message: "#^Parameter \\#1 \\$timezone of class DateTimeZone constructor expects string, mixed given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: src/Command/ExportCreateCommand.php
|
path: src/Command/ExportCreateCommand.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$userIds of method App\\\\Repository\\\\UserRepository\\:\\:findByIds\\(\\) expects array\\<int\\>, mixed given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Command/ExportCreateCommand.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, mixed given\\.$#"
|
|
||||||
count: 4
|
|
||||||
path: src/Command/ExportCreateCommand.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#2 \\$content of method Symfony\\\\Component\\\\Filesystem\\\\Filesystem\\:\\:dumpFile\\(\\) expects resource\\|string, string\\|false given\\.$#"
|
message: "#^Parameter \\#2 \\$content of method Symfony\\\\Component\\\\Filesystem\\\\Filesystem\\:\\:dumpFile\\(\\) expects resource\\|string, string\\|false given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -783,11 +758,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Controller/ActivityController.php
|
path: src/Controller/ActivityController.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\$entries of method App\\\\Export\\\\Spreadsheet\\\\EntityWithMetaFieldsExporter\\:\\:export\\(\\) expects array, iterable\\<App\\\\Entity\\\\Activity\\> given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Controller/ActivityController.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#2 \\$replace of method App\\\\Repository\\\\ActivityRepository\\:\\:deleteActivity\\(\\) expects App\\\\Entity\\\\Activity\\|null, mixed given\\.$#"
|
message: "#^Parameter \\#2 \\$replace of method App\\\\Repository\\\\ActivityRepository\\:\\:deleteActivity\\(\\) expects App\\\\Entity\\\\Activity\\|null, mixed given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -833,11 +803,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Controller/CustomerController.php
|
path: src/Controller/CustomerController.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\$entries of method App\\\\Export\\\\Spreadsheet\\\\EntityWithMetaFieldsExporter\\:\\:export\\(\\) expects array, iterable\\<App\\\\Entity\\\\Customer\\> given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Controller/CustomerController.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#2 \\$replace of method App\\\\Repository\\\\CustomerRepository\\:\\:deleteCustomer\\(\\) expects App\\\\Entity\\\\Customer\\|null, mixed given\\.$#"
|
message: "#^Parameter \\#2 \\$replace of method App\\\\Repository\\\\CustomerRepository\\:\\:deleteCustomer\\(\\) expects App\\\\Entity\\\\Customer\\|null, mixed given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -973,11 +938,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Controller/ProjectController.php
|
path: src/Controller/ProjectController.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\$entries of method App\\\\Export\\\\Spreadsheet\\\\EntityWithMetaFieldsExporter\\:\\:export\\(\\) expects array, iterable\\<App\\\\Entity\\\\Project\\> given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Controller/ProjectController.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#2 \\$replace of method App\\\\Repository\\\\ProjectRepository\\:\\:deleteProject\\(\\) expects App\\\\Entity\\\\Project\\|null, mixed given\\.$#"
|
message: "#^Parameter \\#2 \\$replace of method App\\\\Repository\\\\ProjectRepository\\:\\:deleteProject\\(\\) expects App\\\\Entity\\\\Project\\|null, mixed given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -1133,11 +1093,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Controller/TimesheetAbstractController.php
|
path: src/Controller/TimesheetAbstractController.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\$page of method App\\\\Controller\\\\TimesheetAbstractController\\:\\:getEditForm\\(\\) expects int, mixed given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Controller/TimesheetAbstractController.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^PHPDoc tag @var for variable \\$teams contains generic class Doctrine\\\\Common\\\\Collections\\\\ArrayCollection but does not specify its types\\: TKey, T$#"
|
message: "#^PHPDoc tag @var for variable \\$teams contains generic class Doctrine\\\\Common\\\\Collections\\\\ArrayCollection but does not specify its types\\: TKey, T$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -1548,11 +1503,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Entity/User.php
|
path: src/Entity/User.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Entity\\\\User\\:\\:getTeams\\(\\) should return iterable\\<App\\\\Entity\\\\Team\\> but returns array\\<int, App\\\\Entity\\\\Team\\|null\\>\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Entity/User.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Entity\\\\User\\:\\:getTimezone\\(\\) should return string but returns bool\\|float\\|int\\|string\\|null\\.$#"
|
message: "#^Method App\\\\Entity\\\\User\\:\\:getTimezone\\(\\) should return string but returns bool\\|float\\|int\\|string\\|null\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -2223,16 +2173,6 @@ parameters:
|
|||||||
count: 2
|
count: 2
|
||||||
path: src/Form/Helper/ActivityHelper.php
|
path: src/Form/Helper/ActivityHelper.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|null given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Form/Helper/ActivityHelper.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\$replace of function str_replace expects array\\|string, string\\|null given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Form/Helper/ActivityHelper.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, bool\\|float\\|int\\|string given\\.$#"
|
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, bool\\|float\\|int\\|string given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -2248,16 +2188,6 @@ parameters:
|
|||||||
count: 2
|
count: 2
|
||||||
path: src/Form/Helper/CustomerHelper.php
|
path: src/Form/Helper/CustomerHelper.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|null given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Form/Helper/CustomerHelper.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\$replace of function str_replace expects array\\|string, string\\|null given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Form/Helper/CustomerHelper.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, bool\\|float\\|int\\|string given\\.$#"
|
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, bool\\|float\\|int\\|string given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -2273,16 +2203,6 @@ parameters:
|
|||||||
count: 2
|
count: 2
|
||||||
path: src/Form/Helper/ProjectHelper.php
|
path: src/Form/Helper/ProjectHelper.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$string of function substr expects string, string\\|null given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Form/Helper/ProjectHelper.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\$replace of function str_replace expects array\\|string, string\\|null given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Form/Helper/ProjectHelper.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, bool\\|float\\|int\\|string given\\.$#"
|
message: "#^Parameter \\#3 \\$subject of function str_replace expects array\\|string, bool\\|float\\|int\\|string given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -3338,11 +3258,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Form/Type/SystemConfigurationType.php
|
path: src/Form/Type/SystemConfigurationType.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$teams of method App\\\\Repository\\\\Query\\\\BaseQuery\\:\\:setTeams\\(\\) expects array\\<App\\\\Entity\\\\Team\\>\\|null, iterable\\<App\\\\Entity\\\\Team\\> given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Form/Type/TeamType.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#1 \\$format of static method DateTime\\:\\:createFromFormat\\(\\) expects string, mixed given\\.$#"
|
message: "#^Parameter \\#1 \\$format of static method DateTime\\:\\:createFromFormat\\(\\) expects string, mixed given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -3823,16 +3738,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Model/ActivityStatistic.php
|
path: src/Model/ActivityStatistic.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Model\\\\BudgetStatisticModel\\:\\:setStatistic\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Model/BudgetStatisticModel.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Model\\\\BudgetStatisticModel\\:\\:setStatisticTotal\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Model/BudgetStatisticModel.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\<0, max\\>\\|false given\\.$#"
|
message: "#^Parameter \\#3 \\$length of function substr expects int\\|null, int\\<0, max\\>\\|false given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4078,26 +3983,11 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/ActivityRepository.php
|
path: src/Repository/ActivityRepository.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\ActivityRepository\\:\\:deleteActivity\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/ActivityRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Repository\\\\ActivityRepository\\:\\:getPermissionCriteria\\(\\) has parameter \\$teams with no value type specified in iterable type array\\.$#"
|
message: "#^Method App\\\\Repository\\\\ActivityRepository\\:\\:getPermissionCriteria\\(\\) has parameter \\$teams with no value type specified in iterable type array\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/ActivityRepository.php
|
path: src/Repository/ActivityRepository.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\ActivityRepository\\:\\:saveActivity\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/ActivityRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\.\\.\\.\\$arrays of function array_merge expects array, iterable\\<App\\\\Entity\\\\Team\\> given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/ActivityRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Repository\\\\BookmarkRepository\\:\\:deleteBookmark\\(\\) has no return type specified\\.$#"
|
message: "#^Method App\\\\Repository\\\\BookmarkRepository\\:\\:deleteBookmark\\(\\) has no return type specified\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4153,11 +4043,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/CustomerRepository.php
|
path: src/Repository/CustomerRepository.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\.\\.\\.\\$arrays of function array_merge expects array, iterable\\<App\\\\Entity\\\\Team\\> given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/CustomerRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot access offset 'counter' on mixed\\.$#"
|
message: "#^Cannot access offset 'counter' on mixed\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4193,111 +4078,21 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/InvoiceRepository.php
|
path: src/Repository/InvoiceRepository.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\.\\.\\.\\$arrays of function array_merge expects array, iterable\\<App\\\\Entity\\\\Team\\> given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/InvoiceRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Loader/ActivityLoader.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$results \\(array\\<App\\\\Entity\\\\Activity\\|int\\>\\) of method App\\\\Repository\\\\Loader\\\\ActivityLoader\\:\\:loadResults\\(\\) should be contravariant with parameter \\$results \\(array\\) of method App\\\\Repository\\\\Loader\\\\LoaderInterface\\:\\:loadResults\\(\\)$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Loader/ActivityLoader.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$results \\(array\\<App\\\\Entity\\\\Customer\\|int\\>\\) of method App\\\\Repository\\\\Loader\\\\CustomerLoader\\:\\:loadResults\\(\\) should be contravariant with parameter \\$results \\(array\\) of method App\\\\Repository\\\\Loader\\\\LoaderInterface\\:\\:loadResults\\(\\)$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Loader/CustomerLoader.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\Loader\\\\DefaultLoader\\:\\:loadResults\\(\\) has parameter \\$results with no value type specified in iterable type array\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Loader/DefaultLoader.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$results \\(array\\<App\\\\Entity\\\\Invoice\\|int\\>\\) of method App\\\\Repository\\\\Loader\\\\InvoiceLoader\\:\\:loadResults\\(\\) should be contravariant with parameter \\$results \\(array\\) of method App\\\\Repository\\\\Loader\\\\LoaderInterface\\:\\:loadResults\\(\\)$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Loader/InvoiceLoader.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\Loader\\\\LoaderInterface\\:\\:loadResults\\(\\) has parameter \\$results with no value type specified in iterable type array\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Loader/LoaderInterface.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/Loader/ProjectLoader.php
|
path: src/Repository/Loader/ProjectLoader.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$results \\(array\\<App\\\\Entity\\\\Project\\|int\\>\\) of method App\\\\Repository\\\\Loader\\\\ProjectLoader\\:\\:loadResults\\(\\) should be contravariant with parameter \\$results \\(array\\) of method App\\\\Repository\\\\Loader\\\\LoaderInterface\\:\\:loadResults\\(\\)$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Loader/ProjectLoader.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$results \\(array\\<App\\\\Entity\\\\Team\\|int\\>\\) of method App\\\\Repository\\\\Loader\\\\TeamLoader\\:\\:loadResults\\(\\) should be contravariant with parameter \\$results \\(array\\) of method App\\\\Repository\\\\Loader\\\\LoaderInterface\\:\\:loadResults\\(\\)$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Loader/TeamLoader.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Customer\\|null\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/Loader/TimesheetLoader.php
|
path: src/Repository/Loader/TimesheetLoader.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot call method getId\\(\\) on App\\\\Entity\\\\Project\\|null\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Loader/TimesheetLoader.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$results \\(array\\<App\\\\Entity\\\\Timesheet\\|int\\>\\) of method App\\\\Repository\\\\Loader\\\\TimesheetLoader\\:\\:loadResults\\(\\) should be contravariant with parameter \\$results \\(array\\) of method App\\\\Repository\\\\Loader\\\\LoaderInterface\\:\\:loadResults\\(\\)$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Loader/TimesheetLoader.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$results \\(array\\<App\\\\Entity\\\\User\\|int\\>\\) of method App\\\\Repository\\\\Loader\\\\UserLoader\\:\\:loadResults\\(\\) should be contravariant with parameter \\$results \\(array\\) of method App\\\\Repository\\\\Loader\\\\LoaderInterface\\:\\:loadResults\\(\\)$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Loader/UserLoader.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\Paginator\\\\LoaderPaginator\\:\\:getAll\\(\\) return type has no value type specified in iterable type iterable\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Paginator/LoaderPaginator.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\Paginator\\\\LoaderPaginator\\:\\:getNbResults\\(\\) should return int\\<0, max\\> but returns int\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Paginator/LoaderPaginator.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$results of method App\\\\Repository\\\\Loader\\\\LoaderInterface\\:\\:loadResults\\(\\) expects array, mixed given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Paginator/LoaderPaginator.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Interface App\\\\Repository\\\\Paginator\\\\PaginatorInterface extends generic interface Pagerfanta\\\\Adapter\\\\AdapterInterface but does not specify its types\\: T$#"
|
message: "#^Interface App\\\\Repository\\\\Paginator\\\\PaginatorInterface extends generic interface Pagerfanta\\\\Adapter\\\\AdapterInterface but does not specify its types\\: T$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/Paginator/PaginatorInterface.php
|
path: src/Repository/Paginator/PaginatorInterface.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\Paginator\\\\PaginatorInterface\\:\\:getAll\\(\\) return type has no value type specified in iterable type iterable\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Paginator/PaginatorInterface.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\Paginator\\\\QueryBuilderPaginator\\:\\:getAll\\(\\) return type has no value type specified in iterable type iterable\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Paginator/QueryBuilderPaginator.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\Paginator\\\\QueryBuilderPaginator\\:\\:getNbResults\\(\\) should return int\\<0, max\\> but returns int\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Paginator/QueryBuilderPaginator.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot call method getSearchFields\\(\\) on App\\\\Utils\\\\SearchTerm\\|null\\.$#"
|
message: "#^Cannot call method getSearchFields\\(\\) on App\\\\Utils\\\\SearchTerm\\|null\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4318,16 +4113,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/ProjectRepository.php
|
path: src/Repository/ProjectRepository.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\ProjectRepository\\:\\:deleteComment\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/ProjectRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\ProjectRepository\\:\\:deleteProject\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/ProjectRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Repository\\\\ProjectRepository\\:\\:getComments\\(\\) return type has no value type specified in iterable type array\\.$#"
|
message: "#^Method App\\\\Repository\\\\ProjectRepository\\:\\:getComments\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4338,31 +4123,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/ProjectRepository.php
|
path: src/Repository/ProjectRepository.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\ProjectRepository\\:\\:getProjectsForQuery\\(\\) should return iterable\\<App\\\\Entity\\\\Project\\> but returns mixed\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/ProjectRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\ProjectRepository\\:\\:saveComment\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/ProjectRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\ProjectRepository\\:\\:saveProject\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/ProjectRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$results of method App\\\\Repository\\\\Loader\\\\ProjectLoader\\:\\:loadResults\\(\\) expects array\\<App\\\\Entity\\\\Project\\|int\\>, mixed given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/ProjectRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\.\\.\\.\\$arrays of function array_merge expects array, iterable\\<App\\\\Entity\\\\Team\\> given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/ProjectRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Repository\\\\Query\\\\BaseQuery\\:\\:getOrderGroups\\(\\) return type has no value type specified in iterable type array\\.$#"
|
message: "#^Method App\\\\Repository\\\\Query\\\\BaseQuery\\:\\:getOrderGroups\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4408,46 +4168,11 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/Query/TimesheetQuery.php
|
path: src/Repository/Query/TimesheetQuery.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot access offset 'counter' on mixed\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Result/TimesheetResult.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot access offset 'duration' on mixed\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Result/TimesheetResult.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\Result\\\\TimesheetResult\\:\\:getResults\\(\\) should return array\\<App\\\\Entity\\\\Timesheet\\> but returns mixed\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Result/TimesheetResult.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Repository\\\\Result\\\\TimesheetResult\\:\\:toIterable\\(\\) return type has no value type specified in iterable type iterable\\.$#"
|
message: "#^Method App\\\\Repository\\\\Result\\\\TimesheetResult\\:\\:toIterable\\(\\) return type has no value type specified in iterable type iterable\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/Result/TimesheetResult.php
|
path: src/Repository/Result/TimesheetResult.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$count of class App\\\\Repository\\\\Result\\\\TimesheetResultStatistic constructor expects int, mixed given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Result/TimesheetResult.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$results of method App\\\\Repository\\\\Loader\\\\TimesheetLoader\\:\\:loadResults\\(\\) expects array\\<App\\\\Entity\\\\Timesheet\\|int\\>, mixed given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Result/TimesheetResult.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\$duration of class App\\\\Repository\\\\Result\\\\TimesheetResultStatistic constructor expects int, mixed given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Result/TimesheetResult.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Property App\\\\Repository\\\\Result\\\\TimesheetResult\\:\\:\\$resultCache \\(array\\<App\\\\Entity\\\\Timesheet\\>\\|null\\) does not accept mixed\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/Result/TimesheetResult.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Repository\\\\RoleRepository\\:\\:deleteRole\\(\\) has no return type specified\\.$#"
|
message: "#^Method App\\\\Repository\\\\RoleRepository\\:\\:deleteRole\\(\\) has no return type specified\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4468,36 +4193,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/TagRepository.php
|
path: src/Repository/TagRepository.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\TeamRepository\\:\\:addPermissionCriteria\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/TeamRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\TeamRepository\\:\\:deleteTeam\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/TeamRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\TeamRepository\\:\\:removeTeamMember\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/TeamRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\TeamRepository\\:\\:saveTeam\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/TeamRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Return type \\(array\\<App\\\\Entity\\\\Team\\>\\) of method App\\\\Repository\\\\TeamRepository\\:\\:findAll\\(\\) should be covariant with return type \\(array\\<int, App\\\\Entity\\\\Team\\>\\) of method Doctrine\\\\Persistence\\\\ObjectRepository\\<App\\\\Entity\\\\Team\\>\\:\\:findAll\\(\\)$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/TeamRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Return type \\(array\\<App\\\\Entity\\\\Team\\>\\) of method App\\\\Repository\\\\TeamRepository\\:\\:findAll\\(\\) should be covariant with return type \\(list\\<App\\\\Entity\\\\Team\\>\\) of method Doctrine\\\\ORM\\\\EntityRepository\\<App\\\\Entity\\\\Team\\>\\:\\:findAll\\(\\)$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/TeamRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Argument of an invalid type array\\<string\\>\\|string supplied for foreach, only iterables are supported\\.$#"
|
message: "#^Argument of an invalid type array\\<string\\>\\|string supplied for foreach, only iterables are supported\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4563,21 +4258,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/TimesheetRepository.php
|
path: src/Repository/TimesheetRepository.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\TimesheetRepository\\:\\:findTimesheetsById\\(\\) should return array\\<App\\\\Entity\\\\Timesheet\\> but returns iterable\\<App\\\\Entity\\\\Timesheet\\>\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/TimesheetRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\TimesheetRepository\\:\\:getActiveEntries\\(\\) should return array\\<App\\\\Entity\\\\Timesheet\\> but returns iterable\\<App\\\\Entity\\\\Timesheet\\>\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/TimesheetRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\TimesheetRepository\\:\\:getHydratedResultsByQuery\\(\\) should return iterable\\<App\\\\Entity\\\\Timesheet\\> but returns mixed\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/TimesheetRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Repository\\\\TimesheetRepository\\:\\:getRawData\\(\\) return type has no value type specified in iterable type array\\.$#"
|
message: "#^Method App\\\\Repository\\\\TimesheetRepository\\:\\:getRawData\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4588,11 +4268,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/TimesheetRepository.php
|
path: src/Repository/TimesheetRepository.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\TimesheetRepository\\:\\:setExported\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/TimesheetRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#1 \\$amountThisMonth of method App\\\\Model\\\\TimesheetStatistic\\:\\:setAmountThisMonth\\(\\) expects float\\|int, mixed given\\.$#"
|
message: "#^Parameter \\#1 \\$amountThisMonth of method App\\\\Model\\\\TimesheetStatistic\\:\\:setAmountThisMonth\\(\\) expects float\\|int, mixed given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4623,21 +4298,11 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/TimesheetRepository.php
|
path: src/Repository/TimesheetRepository.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$results of method App\\\\Repository\\\\Loader\\\\TimesheetLoader\\:\\:loadResults\\(\\) expects array\\<App\\\\Entity\\\\Timesheet\\|int\\>, mixed given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/TimesheetRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#2 \\$amount of class App\\\\Model\\\\Revenue constructor expects float, mixed given\\.$#"
|
message: "#^Parameter \\#2 \\$amount of class App\\\\Model\\\\Revenue constructor expects float, mixed given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/TimesheetRepository.php
|
path: src/Repository/TimesheetRepository.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\.\\.\\.\\$arrays of function array_merge expects array, iterable\\<App\\\\Entity\\\\Team\\> given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/TimesheetRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot call method getSearchFields\\(\\) on App\\\\Utils\\\\SearchTerm\\|null\\.$#"
|
message: "#^Cannot call method getSearchFields\\(\\) on App\\\\Utils\\\\SearchTerm\\|null\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -4653,16 +4318,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Repository/UserRepository.php
|
path: src/Repository/UserRepository.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\UserRepository\\:\\:addPermissionCriteria\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/UserRepository.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Repository\\\\UserRepository\\:\\:deleteUser\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Repository/UserRepository.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Repository\\\\UserRepository\\:\\:findByUsername\\(\\) has parameter \\$username with no type specified\\.$#"
|
message: "#^Method App\\\\Repository\\\\UserRepository\\:\\:findByUsername\\(\\) has parameter \\$username with no type specified\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -5488,16 +5143,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Widget/Type/PaginatedWorkingTimeChart.php
|
path: src/Widget/Type/PaginatedWorkingTimeChart.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$results of method App\\\\Repository\\\\Loader\\\\TeamLoader\\:\\:loadResults\\(\\) expects array\\<App\\\\Entity\\\\Team\\|int\\>, iterable\\<App\\\\Entity\\\\Team\\> given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Widget/Type/UserTeamProjects.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$results of method App\\\\Repository\\\\Loader\\\\UserLoader\\:\\:loadResults\\(\\) expects array\\<App\\\\Entity\\\\User\\|int\\>, array\\<int, int\\|null\\> given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Widget/Type/UserTeams.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Property App\\\\Widget\\\\WidgetService\\:\\:\\$widgets type has no value type specified in iterable type array\\.$#"
|
message: "#^Property App\\\\Widget\\\\WidgetService\\:\\:\\$widgets type has no value type specified in iterable type array\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ use App\Kernel;
|
|||||||
|
|
||||||
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
|
||||||
|
|
||||||
|
// TODO remove once PARTIAL usage was replaced entirely
|
||||||
|
\Doctrine\Deprecations\Deprecation::ignoreDeprecations('https://github.com/doctrine/orm/issues/8471');
|
||||||
|
|
||||||
return function (array $context) {
|
return function (array $context) {
|
||||||
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ final class ActivityController extends BaseApiController
|
|||||||
$user = $this->getUser();
|
$user = $this->getUser();
|
||||||
|
|
||||||
$query = new ActivityQuery();
|
$query = new ActivityQuery();
|
||||||
|
$query->loadTeams();
|
||||||
$query->setCurrentUser($user);
|
$query->setCurrentUser($user);
|
||||||
|
|
||||||
$order = $paramFetcher->get('order');
|
$order = $paramFetcher->get('order');
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ final class CustomerController extends BaseApiController
|
|||||||
$user = $this->getUser();
|
$user = $this->getUser();
|
||||||
|
|
||||||
$query = new CustomerQuery();
|
$query = new CustomerQuery();
|
||||||
|
$query->loadTeams();
|
||||||
$query->setCurrentUser($user);
|
$query->setCurrentUser($user);
|
||||||
|
|
||||||
$order = $paramFetcher->get('order');
|
$order = $paramFetcher->get('order');
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ final class ProjectController extends BaseApiController
|
|||||||
$user = $this->getUser();
|
$user = $this->getUser();
|
||||||
|
|
||||||
$query = new ProjectQuery();
|
$query = new ProjectQuery();
|
||||||
|
$query->loadTeams();
|
||||||
$query->setCurrentUser($user);
|
$query->setCurrentUser($user);
|
||||||
|
|
||||||
$order = $paramFetcher->get('order');
|
$order = $paramFetcher->get('order');
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use App\Form\API\TeamApiEditForm;
|
|||||||
use App\Repository\ActivityRepository;
|
use App\Repository\ActivityRepository;
|
||||||
use App\Repository\CustomerRepository;
|
use App\Repository\CustomerRepository;
|
||||||
use App\Repository\ProjectRepository;
|
use App\Repository\ProjectRepository;
|
||||||
|
use App\Repository\Query\TeamQuery;
|
||||||
use App\Repository\TeamRepository;
|
use App\Repository\TeamRepository;
|
||||||
use FOS\RestBundle\View\View;
|
use FOS\RestBundle\View\View;
|
||||||
use FOS\RestBundle\View\ViewHandlerInterface;
|
use FOS\RestBundle\View\ViewHandlerInterface;
|
||||||
@@ -53,7 +54,10 @@ final class TeamController extends BaseApiController
|
|||||||
#[Route(methods: ['GET'], path: '', name: 'get_teams')]
|
#[Route(methods: ['GET'], path: '', name: 'get_teams')]
|
||||||
public function cgetAction(): Response
|
public function cgetAction(): Response
|
||||||
{
|
{
|
||||||
$data = $this->repository->findAll();
|
$query = new TeamQuery();
|
||||||
|
$query->setCurrentUser($this->getUser());
|
||||||
|
|
||||||
|
$data = $this->repository->getTeamsForQuery($query);
|
||||||
|
|
||||||
$view = new View($data, 200);
|
$view = new View($data, 200);
|
||||||
$view->getContext()->setGroups(self::GROUPS_COLLECTION);
|
$view->getContext()->setGroups(self::GROUPS_COLLECTION);
|
||||||
|
|||||||
@@ -106,25 +106,25 @@ final class ExportCreateCommand extends Command
|
|||||||
$timezone = new \DateTimeZone($timezone);
|
$timezone = new \DateTimeZone($timezone);
|
||||||
$dateFactory = new DateTimeFactory($timezone);
|
$dateFactory = new DateTimeFactory($timezone);
|
||||||
|
|
||||||
$customerIDs = $input->getOption('customer');
|
$customerIDs = $this->optionToIntArray($input, 'customer');
|
||||||
$customers = [];
|
$customers = [];
|
||||||
if (\count($customerIDs) > 0) {
|
if (\count($customerIDs) > 0) {
|
||||||
$customers = $this->customerRepository->findByIds($customerIDs);
|
$customers = $this->customerRepository->findByIds($customerIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
$projectIDs = $input->getOption('project');
|
$projectIDs = $this->optionToIntArray($input, 'project');
|
||||||
$projects = [];
|
$projects = [];
|
||||||
if (\count($projectIDs) > 0) {
|
if (\count($projectIDs) > 0) {
|
||||||
$projects = $this->projectRepository->findByIds($projectIDs);
|
$projects = $this->projectRepository->findByIds($projectIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
$teamIDs = $input->getOption('team');
|
$teamIDs = $this->optionToIntArray($input, 'team');
|
||||||
$teams = [];
|
$teams = [];
|
||||||
if (\count($teamIDs) > 0) {
|
if (\count($teamIDs) > 0) {
|
||||||
$teams = $this->teamRepository->findByIds($teamIDs);
|
$teams = $this->teamRepository->findByIds($teamIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
$userIDs = $input->getOption('user');
|
$userIDs = $this->optionToIntArray($input, 'user');
|
||||||
$users = [];
|
$users = [];
|
||||||
if (\count($userIDs) > 0) {
|
if (\count($userIDs) > 0) {
|
||||||
$users = $this->userRepository->findByIds($userIDs);
|
$users = $this->userRepository->findByIds($userIDs);
|
||||||
@@ -287,6 +287,25 @@ final class ExportCreateCommand extends Command
|
|||||||
return Command::SUCCESS;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int>
|
||||||
|
*/
|
||||||
|
private function optionToIntArray(InputInterface $input, string $name): array
|
||||||
|
{
|
||||||
|
$results = [];
|
||||||
|
|
||||||
|
$options = $input->getOption($name);
|
||||||
|
if (\is_array($options) && \count($options) > 0) {
|
||||||
|
foreach ($options as $option) {
|
||||||
|
if (is_numeric($option)) {
|
||||||
|
$results[] = (int) $option;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
private function savePreview(Response $response, string $directory): string
|
private function savePreview(Response $response, string $directory): string
|
||||||
{
|
{
|
||||||
$filename = uniqid('invoice_');
|
$filename = uniqid('invoice_');
|
||||||
|
|||||||
@@ -51,7 +51,12 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|||||||
#[Route(path: '/admin/activity')]
|
#[Route(path: '/admin/activity')]
|
||||||
final class ActivityController extends AbstractController
|
final class ActivityController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(private ActivityRepository $repository, private SystemConfiguration $configuration, private EventDispatcherInterface $dispatcher, private ActivityService $activityService)
|
public function __construct(
|
||||||
|
private readonly ActivityRepository $repository,
|
||||||
|
private readonly SystemConfiguration $configuration,
|
||||||
|
private readonly EventDispatcherInterface $dispatcher,
|
||||||
|
private readonly ActivityService $activityService
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +66,7 @@ final class ActivityController extends AbstractController
|
|||||||
public function indexAction(int $page, Request $request): Response
|
public function indexAction(int $page, Request $request): Response
|
||||||
{
|
{
|
||||||
$query = new ActivityQuery();
|
$query = new ActivityQuery();
|
||||||
|
$query->loadTeams();
|
||||||
$query->setCurrentUser($this->getUser());
|
$query->setCurrentUser($this->getUser());
|
||||||
$query->setPage($page);
|
$query->setPage($page);
|
||||||
|
|
||||||
@@ -443,7 +449,6 @@ final class ActivityController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ActivityQuery $query
|
|
||||||
* @return FormInterface<ActivityQuery>
|
* @return FormInterface<ActivityQuery>
|
||||||
*/
|
*/
|
||||||
private function getToolbarForm(ActivityQuery $query): FormInterface
|
private function getToolbarForm(ActivityQuery $query): FormInterface
|
||||||
@@ -456,7 +461,7 @@ final class ActivityController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return FormInterface<ActivityEditForm>
|
* @return FormInterface<mixed>
|
||||||
*/
|
*/
|
||||||
private function createEditForm(Activity $activity): FormInterface
|
private function createEditForm(Activity $activity): FormInterface
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -54,7 +54,10 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|||||||
#[Route(path: '/admin/customer')]
|
#[Route(path: '/admin/customer')]
|
||||||
final class CustomerController extends AbstractController
|
final class CustomerController extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(private CustomerRepository $repository, private EventDispatcherInterface $dispatcher)
|
public function __construct(
|
||||||
|
private readonly CustomerRepository $repository,
|
||||||
|
private readonly EventDispatcherInterface $dispatcher
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +67,7 @@ final class CustomerController extends AbstractController
|
|||||||
public function indexAction(int $page, Request $request): Response
|
public function indexAction(int $page, Request $request): Response
|
||||||
{
|
{
|
||||||
$query = new CustomerQuery();
|
$query = new CustomerQuery();
|
||||||
|
$query->loadTeams();
|
||||||
$query->setCurrentUser($this->getUser());
|
$query->setCurrentUser($this->getUser());
|
||||||
$query->setPage($page);
|
$query->setPage($page);
|
||||||
|
|
||||||
@@ -126,7 +130,6 @@ final class CustomerController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param CustomerQuery $query
|
|
||||||
* @return MetaTableTypeInterface[]
|
* @return MetaTableTypeInterface[]
|
||||||
*/
|
*/
|
||||||
private function findMetaColumns(CustomerQuery $query): array
|
private function findMetaColumns(CustomerQuery $query): array
|
||||||
@@ -539,7 +542,7 @@ final class CustomerController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return FormInterface<CustomerComment>
|
* @return FormInterface<mixed>
|
||||||
*/
|
*/
|
||||||
private function getCommentForm(CustomerComment $comment): FormInterface
|
private function getCommentForm(CustomerComment $comment): FormInterface
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ final class ProjectController extends AbstractController
|
|||||||
public function indexAction(int $page, Request $request): Response
|
public function indexAction(int $page, Request $request): Response
|
||||||
{
|
{
|
||||||
$query = new ProjectQuery();
|
$query = new ProjectQuery();
|
||||||
|
$query->loadTeams();
|
||||||
$query->setCurrentUser($this->getUser());
|
$query->setCurrentUser($this->getUser());
|
||||||
$query->setPage($page);
|
$query->setPage($page);
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ final class QuickEntryController extends AbstractController
|
|||||||
$result = $this->repository->getTimesheetResult($query);
|
$result = $this->repository->getTimesheetResult($query);
|
||||||
|
|
||||||
$rows = [];
|
$rows = [];
|
||||||
foreach ($result->getResults(true) as $timesheet) {
|
foreach ($result->getResults() as $timesheet) {
|
||||||
$i = 0;
|
$i = 0;
|
||||||
$id = $timesheet->getProject()->getId() . '_' . $timesheet->getActivity()->getId();
|
$id = $timesheet->getProject()->getId() . '_' . $timesheet->getActivity()->getId();
|
||||||
$day = $timesheet->getBegin()->format('Y-m-d');
|
$day = $timesheet->getBegin()->format('Y-m-d');
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ use App\Reporting\YearByUser\YearByUser;
|
|||||||
use App\Reporting\YearByUser\YearByUserForm;
|
use App\Reporting\YearByUser\YearByUserForm;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
use Exception;
|
|
||||||
use PhpOffice\PhpSpreadsheet\Reader\Html;
|
use PhpOffice\PhpSpreadsheet\Reader\Html;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@@ -31,11 +30,6 @@ use Symfony\Component\Security\Http\Attribute\IsGranted;
|
|||||||
#[IsGranted('report:user')]
|
#[IsGranted('report:user')]
|
||||||
final class UserYearController extends AbstractUserReportController
|
final class UserYearController extends AbstractUserReportController
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @param Request $request
|
|
||||||
* @return Response
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
#[Route(path: '/year', name: 'report_user_year', methods: ['GET', 'POST'])]
|
#[Route(path: '/year', name: 'report_user_year', methods: ['GET', 'POST'])]
|
||||||
public function yearByUser(Request $request, SystemConfiguration $systemConfiguration): Response
|
public function yearByUser(Request $request, SystemConfiguration $systemConfiguration): Response
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ use App\Utils\PageSetup;
|
|||||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
use Symfony\Component\Form\FormError;
|
use Symfony\Component\Form\FormError;
|
||||||
use Symfony\Component\Form\FormInterface;
|
use Symfony\Component\Form\FormInterface;
|
||||||
|
use Symfony\Component\Form\FormTypeInterface;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
@@ -144,7 +145,9 @@ abstract class TimesheetAbstractController extends AbstractController
|
|||||||
$event = new TimesheetMetaDefinitionEvent($entry);
|
$event = new TimesheetMetaDefinitionEvent($entry);
|
||||||
$this->dispatcher->dispatch($event);
|
$this->dispatcher->dispatch($event);
|
||||||
|
|
||||||
$editForm = $this->getEditForm($entry, $request->get('page'));
|
$page = $request->get('page');
|
||||||
|
$page = is_numeric($page) ? (int) $page : 1;
|
||||||
|
$editForm = $this->getEditForm($entry, $page);
|
||||||
$editForm->handleRequest($request);
|
$editForm->handleRequest($request);
|
||||||
|
|
||||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
||||||
@@ -267,7 +270,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
|||||||
if (null === $exporter) {
|
if (null === $exporter) {
|
||||||
$form->addError(new FormError('Invalid timesheet exporter given'));
|
$form->addError(new FormError('Invalid timesheet exporter given'));
|
||||||
} else {
|
} else {
|
||||||
return $exporter->render($entries->getResults(true), $query);
|
return $exporter->render($entries->getResults(), $query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -484,6 +487,9 @@ abstract class TimesheetAbstractController extends AbstractController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param class-string<FormTypeInterface> $formClass
|
||||||
|
*/
|
||||||
protected function generateCreateForm(Timesheet $entry, string $formClass, string $action): FormInterface
|
protected function generateCreateForm(Timesheet $entry, string $formClass, string $action): FormInterface
|
||||||
{
|
{
|
||||||
$mode = $this->getTrackingMode();
|
$mode = $this->getTrackingMode();
|
||||||
@@ -504,12 +510,7 @@ abstract class TimesheetAbstractController extends AbstractController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function getEditForm(Timesheet $entry, int $page): FormInterface
|
||||||
* @param Timesheet $entry
|
|
||||||
* @param int $page
|
|
||||||
* @return FormInterface
|
|
||||||
*/
|
|
||||||
protected function getEditForm(Timesheet $entry, $page): FormInterface
|
|
||||||
{
|
{
|
||||||
$mode = $this->getTrackingMode();
|
$mode = $this->getTrackingMode();
|
||||||
|
|
||||||
@@ -568,6 +569,9 @@ abstract class TimesheetAbstractController extends AbstractController
|
|||||||
return 'edit_rate_own_timesheet';
|
return 'edit_rate_own_timesheet';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return class-string<FormTypeInterface>
|
||||||
|
*/
|
||||||
protected function getEditFormClassName(): string
|
protected function getEditFormClassName(): string
|
||||||
{
|
{
|
||||||
return TimesheetEditForm::class;
|
return TimesheetEditForm::class;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
#[ORM\Entity(repositoryClass: 'App\Repository\CustomerRepository')]
|
#[ORM\Entity(repositoryClass: 'App\Repository\CustomerRepository')]
|
||||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||||
#[Serializer\ExclusionPolicy('all')]
|
#[Serializer\ExclusionPolicy('all')]
|
||||||
#[Exporter\Order(['id', 'name', 'company', 'number', 'vatId', 'address', 'contact', 'email', 'phone', 'mobile', 'fax', 'homepage', 'country', 'currency', 'timezone', 'budget', 'timeBudget', 'budgetType', 'color', 'visible', 'teams', 'comment', 'billable'])]
|
#[Exporter\Order(['id', 'name', 'company', 'number', 'vatId', 'address', 'contact', 'email', 'phone', 'mobile', 'fax', 'homepage', 'country', 'currency', 'timezone', 'budget', 'timeBudget', 'budgetType', 'color', 'visible', 'comment', 'billable'])]
|
||||||
#[Constraints\Customer]
|
#[Constraints\Customer]
|
||||||
class Customer implements EntityWithMetaFields, EntityWithBudget
|
class Customer implements EntityWithMetaFields, EntityWithBudget
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
#[Serializer\ExclusionPolicy('all')]
|
#[Serializer\ExclusionPolicy('all')]
|
||||||
#[Serializer\VirtualProperty('CustomerName', exp: 'object.getCustomer() === null ? null : object.getCustomer().getName()', options: [new Serializer\SerializedName('parentTitle'), new Serializer\Type(name: 'string'), new Serializer\Groups(['Project'])])]
|
#[Serializer\VirtualProperty('CustomerName', exp: 'object.getCustomer() === null ? null : object.getCustomer().getName()', options: [new Serializer\SerializedName('parentTitle'), new Serializer\Type(name: 'string'), new Serializer\Groups(['Project'])])]
|
||||||
#[Serializer\VirtualProperty('CustomerAsId', exp: 'object.getCustomer() === null ? null : object.getCustomer().getId()', options: [new Serializer\SerializedName('customer'), new Serializer\Type(name: 'integer'), new Serializer\Groups(['Project', 'Team', 'Not_Expanded'])])]
|
#[Serializer\VirtualProperty('CustomerAsId', exp: 'object.getCustomer() === null ? null : object.getCustomer().getId()', options: [new Serializer\SerializedName('customer'), new Serializer\Type(name: 'integer'), new Serializer\Groups(['Project', 'Team', 'Not_Expanded'])])]
|
||||||
#[Exporter\Order(['id', 'name', 'customer', 'orderNumber', 'orderDate', 'start', 'end', 'budget', 'timeBudget', 'budgetType', 'color', 'visible', 'teams', 'comment', 'billable', 'number'])]
|
#[Exporter\Order(['id', 'name', 'customer', 'orderNumber', 'orderDate', 'start', 'end', 'budget', 'timeBudget', 'budgetType', 'color', 'visible', 'comment', 'billable', 'number'])]
|
||||||
#[Exporter\Expose(name: 'customer', label: 'customer', exp: 'object.getCustomer() === null ? null : object.getCustomer().getName()')]
|
#[Exporter\Expose(name: 'customer', label: 'customer', exp: 'object.getCustomer() === null ? null : object.getCustomer().getName()')]
|
||||||
#[Constraints\Project]
|
#[Constraints\Project]
|
||||||
class Project implements EntityWithMetaFields, EntityWithBudget
|
class Project implements EntityWithMetaFields, EntityWithBudget
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ class Tag
|
|||||||
/**
|
/**
|
||||||
* This is ONLY here, so we can count the amount of timesheets.
|
* This is ONLY here, so we can count the amount of timesheets.
|
||||||
*
|
*
|
||||||
|
* See TagRepository "SIZE(t.timesheets)"
|
||||||
|
* Removing this makes the count more complicated.
|
||||||
|
* Should be refactored at some point in the future.
|
||||||
|
*
|
||||||
* @var Collection<Timesheet>
|
* @var Collection<Timesheet>
|
||||||
*/
|
*/
|
||||||
#[ORM\ManyToMany(targetEntity: Timesheet::class, mappedBy: 'tags', fetch: 'EXTRA_LAZY')]
|
#[ORM\ManyToMany(targetEntity: Timesheet::class, mappedBy: 'tags', fetch: 'EXTRA_LAZY')]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Repository\TeamRepository;
|
||||||
use App\Validator\Constraints as Constraints;
|
use App\Validator\Constraints as Constraints;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
@@ -20,7 +21,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
|
|
||||||
#[ORM\Table(name: 'kimai2_teams')]
|
#[ORM\Table(name: 'kimai2_teams')]
|
||||||
#[ORM\UniqueConstraint(columns: ['name'])]
|
#[ORM\UniqueConstraint(columns: ['name'])]
|
||||||
#[ORM\Entity(repositoryClass: 'App\Repository\TeamRepository')]
|
#[ORM\Entity(repositoryClass: TeamRepository::class)]
|
||||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||||
#[UniqueEntity('name')]
|
#[UniqueEntity('name')]
|
||||||
#[Serializer\ExclusionPolicy('all')]
|
#[Serializer\ExclusionPolicy('all')]
|
||||||
@@ -310,6 +311,7 @@ class Team
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @internal
|
||||||
* @return Collection<Customer>
|
* @return Collection<Customer>
|
||||||
*/
|
*/
|
||||||
public function getCustomers(): iterable
|
public function getCustomers(): iterable
|
||||||
@@ -343,6 +345,7 @@ class Team
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @internal
|
||||||
* @return Collection<Project>
|
* @return Collection<Project>
|
||||||
*/
|
*/
|
||||||
public function getProjects(): iterable
|
public function getProjects(): iterable
|
||||||
@@ -376,6 +379,7 @@ class Team
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @internal
|
||||||
* @return Collection<Activity>
|
* @return Collection<Activity>
|
||||||
*/
|
*/
|
||||||
public function getActivities(): iterable
|
public function getActivities(): iterable
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
|
|||||||
* @var Collection<UserPreference>|null
|
* @var Collection<UserPreference>|null
|
||||||
*/
|
*/
|
||||||
#[ORM\OneToMany(mappedBy: 'user', targetEntity: UserPreference::class, cascade: ['persist'])]
|
#[ORM\OneToMany(mappedBy: 'user', targetEntity: UserPreference::class, cascade: ['persist'])]
|
||||||
private ?Collection $preferences = null;
|
private ?Collection $preferences;
|
||||||
/**
|
/**
|
||||||
* List of all team memberships.
|
* List of all team memberships.
|
||||||
*
|
*
|
||||||
@@ -663,12 +663,14 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
|
|||||||
#[Serializer\SerializedName('teams')]
|
#[Serializer\SerializedName('teams')]
|
||||||
#[Serializer\Groups(['User_Entity'])]
|
#[Serializer\Groups(['User_Entity'])]
|
||||||
#[OA\Property(type: 'array', items: new OA\Items(ref: '#/components/schemas/Team'))]
|
#[OA\Property(type: 'array', items: new OA\Items(ref: '#/components/schemas/Team'))]
|
||||||
public function getTeams(): iterable
|
public function getTeams(): array
|
||||||
{
|
{
|
||||||
$teams = [];
|
$teams = [];
|
||||||
foreach ($this->memberships as $membership) {
|
foreach ($this->memberships as $membership) {
|
||||||
|
if ($membership->getTeam() !== null) {
|
||||||
$teams[] = $membership->getTeam();
|
$teams[] = $membership->getTeam();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $teams;
|
return $teams;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ use App\Repository\Query\CustomerQuery;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically find possible meta fields for a customer query.
|
* Dynamically find possible meta fields for a customer query.
|
||||||
*
|
|
||||||
* @method CustomerQuery getQuery()
|
|
||||||
*/
|
*/
|
||||||
final class CustomerMetaDisplayEvent extends AbstractMetaDisplayEvent
|
final class CustomerMetaDisplayEvent extends AbstractMetaDisplayEvent
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,13 +32,12 @@ final class InvoiceSubscriber extends AbstractActionsSubscriber
|
|||||||
|
|
||||||
$allowCreate = $this->isGranted('create_invoice');
|
$allowCreate = $this->isGranted('create_invoice');
|
||||||
$allowView = $this->isGranted('view_invoice');
|
$allowView = $this->isGranted('view_invoice');
|
||||||
$allowCustomer = $this->isGranted('access', $invoice->getCustomer());
|
|
||||||
|
|
||||||
if ($allowCustomer && $allowCreate) {
|
if ($allowCreate) {
|
||||||
$event->addEdit($this->path('admin_invoice_edit', ['id' => $invoice->getId()]));
|
$event->addEdit($this->path('admin_invoice_edit', ['id' => $invoice->getId()]));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($allowCustomer && $allowView) {
|
if ($allowView) {
|
||||||
$event->addAction('download', ['url' => $this->path('admin_invoice_download', ['id' => $invoice->getId()]), 'target' => '_blank']);
|
$event->addAction('download', ['url' => $this->path('admin_invoice_download', ['id' => $invoice->getId()]), 'target' => '_blank']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +45,7 @@ final class InvoiceSubscriber extends AbstractActionsSubscriber
|
|||||||
$event->addDivider();
|
$event->addDivider();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($allowCustomer && $allowCreate) {
|
if ($allowCreate) {
|
||||||
if (!$invoice->isPending()) {
|
if (!$invoice->isPending()) {
|
||||||
$event->addAction('invoice.pending', ['url' => $this->path('admin_invoice_status', ['id' => $invoice->getId(), 'status' => 'pending', 'token' => $payload['token']])]);
|
$event->addAction('invoice.pending', ['url' => $this->path('admin_invoice_status', ['id' => $invoice->getId(), 'status' => 'pending', 'token' => $payload['token']])]);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ use App\Event\ProjectMetaDisplayEvent;
|
|||||||
use App\Event\TimesheetMetaDisplayEvent;
|
use App\Event\TimesheetMetaDisplayEvent;
|
||||||
use App\Event\UserPreferenceDisplayEvent;
|
use App\Event\UserPreferenceDisplayEvent;
|
||||||
use App\Export\ExportFilename;
|
use App\Export\ExportFilename;
|
||||||
|
use App\Repository\Query\ActivityQuery;
|
||||||
use App\Repository\Query\CustomerQuery;
|
use App\Repository\Query\CustomerQuery;
|
||||||
|
use App\Repository\Query\ProjectQuery;
|
||||||
use App\Repository\Query\TimesheetQuery;
|
use App\Repository\Query\TimesheetQuery;
|
||||||
use App\Twig\LocaleFormatExtensions;
|
use App\Twig\LocaleFormatExtensions;
|
||||||
use App\Utils\StringHelper;
|
use App\Utils\StringHelper;
|
||||||
@@ -461,9 +463,7 @@ abstract class AbstractSpreadsheetRenderer
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($columns['customer-meta'])) {
|
if (isset($columns['customer-meta'])) {
|
||||||
/** @var CustomerQuery $customerQuery */
|
$customerMetaFields = $this->findMetaColumns(new CustomerMetaDisplayEvent($query->copyTo(new CustomerQuery()), CustomerMetaDisplayEvent::EXPORT));
|
||||||
$customerQuery = $query->copyTo(new CustomerQuery());
|
|
||||||
$customerMetaFields = $this->findMetaColumns(new CustomerMetaDisplayEvent($customerQuery, CustomerMetaDisplayEvent::EXPORT));
|
|
||||||
|
|
||||||
$columns['customer-meta'] = [
|
$columns['customer-meta'] = [
|
||||||
'header' => function (Worksheet $sheet, int $row, int $column) use ($customerMetaFields): int {
|
'header' => function (Worksheet $sheet, int $row, int $column) use ($customerMetaFields): int {
|
||||||
@@ -491,7 +491,7 @@ abstract class AbstractSpreadsheetRenderer
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($columns['project-meta'])) {
|
if (isset($columns['project-meta'])) {
|
||||||
$projectMetaFields = $this->findMetaColumns(new ProjectMetaDisplayEvent($query, ProjectMetaDisplayEvent::EXPORT));
|
$projectMetaFields = $this->findMetaColumns(new ProjectMetaDisplayEvent($query->copyTo(new ProjectQuery()), ProjectMetaDisplayEvent::EXPORT));
|
||||||
$columns['project-meta'] = [
|
$columns['project-meta'] = [
|
||||||
'header' => function (Worksheet $sheet, int $row, int $column) use ($projectMetaFields): int {
|
'header' => function (Worksheet $sheet, int $row, int $column) use ($projectMetaFields): int {
|
||||||
foreach ($projectMetaFields as $metaField) {
|
foreach ($projectMetaFields as $metaField) {
|
||||||
@@ -518,7 +518,7 @@ abstract class AbstractSpreadsheetRenderer
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($columns['activity-meta'])) {
|
if (isset($columns['activity-meta'])) {
|
||||||
$activityMetaFields = $this->findMetaColumns(new ActivityMetaDisplayEvent($query, ActivityMetaDisplayEvent::EXPORT));
|
$activityMetaFields = $this->findMetaColumns(new ActivityMetaDisplayEvent($query->copyTo(new ActivityQuery()), ActivityMetaDisplayEvent::EXPORT));
|
||||||
$columns['activity-meta'] = [
|
$columns['activity-meta'] = [
|
||||||
'header' => function (Worksheet $sheet, int $row, int $column) use ($activityMetaFields): int {
|
'header' => function (Worksheet $sheet, int $row, int $column) use ($activityMetaFields): int {
|
||||||
foreach ($activityMetaFields as $metaField) {
|
foreach ($activityMetaFields as $metaField) {
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ use App\Event\ProjectMetaDisplayEvent;
|
|||||||
use App\Event\TimesheetMetaDisplayEvent;
|
use App\Event\TimesheetMetaDisplayEvent;
|
||||||
use App\Event\UserPreferenceDisplayEvent;
|
use App\Event\UserPreferenceDisplayEvent;
|
||||||
use App\Project\ProjectStatisticService;
|
use App\Project\ProjectStatisticService;
|
||||||
|
use App\Repository\Query\ActivityQuery;
|
||||||
use App\Repository\Query\CustomerQuery;
|
use App\Repository\Query\CustomerQuery;
|
||||||
|
use App\Repository\Query\ProjectQuery;
|
||||||
use App\Repository\Query\TimesheetQuery;
|
use App\Repository\Query\TimesheetQuery;
|
||||||
use App\Twig\SecurityPolicy\ExportPolicy;
|
use App\Twig\SecurityPolicy\ExportPolicy;
|
||||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||||
@@ -81,13 +83,10 @@ class HtmlRenderer
|
|||||||
*/
|
*/
|
||||||
public function render(array $timesheets, TimesheetQuery $query): Response
|
public function render(array $timesheets, TimesheetQuery $query): Response
|
||||||
{
|
{
|
||||||
/** @var CustomerQuery $customerQuery */
|
|
||||||
$customerQuery = $query->copyTo(new CustomerQuery());
|
|
||||||
|
|
||||||
$timesheetMetaFields = $this->findMetaColumns(new TimesheetMetaDisplayEvent($query, TimesheetMetaDisplayEvent::EXPORT));
|
$timesheetMetaFields = $this->findMetaColumns(new TimesheetMetaDisplayEvent($query, TimesheetMetaDisplayEvent::EXPORT));
|
||||||
$customerMetaFields = $this->findMetaColumns(new CustomerMetaDisplayEvent($customerQuery, CustomerMetaDisplayEvent::EXPORT));
|
$customerMetaFields = $this->findMetaColumns(new CustomerMetaDisplayEvent($query->copyTo(new CustomerQuery()), CustomerMetaDisplayEvent::EXPORT));
|
||||||
$projectMetaFields = $this->findMetaColumns(new ProjectMetaDisplayEvent($query, ProjectMetaDisplayEvent::EXPORT));
|
$projectMetaFields = $this->findMetaColumns(new ProjectMetaDisplayEvent($query->copyTo(new ProjectQuery()), ProjectMetaDisplayEvent::EXPORT));
|
||||||
$activityMetaFields = $this->findMetaColumns(new ActivityMetaDisplayEvent($query, ActivityMetaDisplayEvent::EXPORT));
|
$activityMetaFields = $this->findMetaColumns(new ActivityMetaDisplayEvent($query->copyTo(new ActivityQuery()), ActivityMetaDisplayEvent::EXPORT));
|
||||||
|
|
||||||
$event = new UserPreferenceDisplayEvent(UserPreferenceDisplayEvent::EXPORT);
|
$event = new UserPreferenceDisplayEvent(UserPreferenceDisplayEvent::EXPORT);
|
||||||
$this->dispatcher->dispatch($event);
|
$this->dispatcher->dispatch($event);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ final class ActivityHelper
|
|||||||
public function getChoiceLabel(Activity $activity): string
|
public function getChoiceLabel(Activity $activity): string
|
||||||
{
|
{
|
||||||
$name = $this->getChoicePattern();
|
$name = $this->getChoicePattern();
|
||||||
$name = str_replace(self::PATTERN_NAME, $activity->getName(), $name);
|
$name = str_replace(self::PATTERN_NAME, $activity->getName() ?? '', $name);
|
||||||
$name = str_replace(self::PATTERN_NUMBER, $activity->getNumber() ?? '', $name);
|
$name = str_replace(self::PATTERN_NUMBER, $activity->getNumber() ?? '', $name);
|
||||||
$name = str_replace(self::PATTERN_COMMENT, $activity->getComment() ?? '', $name);
|
$name = str_replace(self::PATTERN_COMMENT, $activity->getComment() ?? '', $name);
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ final class ActivityHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($name === '' || $name === self::SPACER) {
|
if ($name === '' || $name === self::SPACER) {
|
||||||
$name = $activity->getName();
|
$name = $activity->getName() ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return substr($name, 0, 110);
|
return substr($name, 0, 110);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ final class CustomerHelper
|
|||||||
public function getChoiceLabel(Customer $customer): string
|
public function getChoiceLabel(Customer $customer): string
|
||||||
{
|
{
|
||||||
$name = $this->getChoicePattern();
|
$name = $this->getChoicePattern();
|
||||||
$name = str_replace(self::PATTERN_NAME, $customer->getName(), $name);
|
$name = str_replace(self::PATTERN_NAME, $customer->getName() ?? '', $name);
|
||||||
$name = str_replace(self::PATTERN_COMMENT, $customer->getComment() ?? '', $name);
|
$name = str_replace(self::PATTERN_COMMENT, $customer->getComment() ?? '', $name);
|
||||||
$name = str_replace(self::PATTERN_NUMBER, $customer->getNumber() ?? '', $name);
|
$name = str_replace(self::PATTERN_NUMBER, $customer->getNumber() ?? '', $name);
|
||||||
$name = str_replace(self::PATTERN_COMPANY, $customer->getCompany() ?? '', $name);
|
$name = str_replace(self::PATTERN_COMPANY, $customer->getCompany() ?? '', $name);
|
||||||
@@ -59,7 +59,7 @@ final class CustomerHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($name === '' || $name === self::SPACER) {
|
if ($name === '' || $name === self::SPACER) {
|
||||||
$name = $customer->getName();
|
$name = $customer->getName() ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return substr($name, 0, 110);
|
return substr($name, 0, 110);
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ final class ProjectHelper
|
|||||||
public function getChoiceLabel(Project $project): string
|
public function getChoiceLabel(Project $project): string
|
||||||
{
|
{
|
||||||
$name = $this->getChoicePattern();
|
$name = $this->getChoicePattern();
|
||||||
$name = str_replace(self::PATTERN_NAME, $project->getName(), $name);
|
$name = str_replace(self::PATTERN_NAME, $project->getName() ?? '', $name);
|
||||||
$name = str_replace(self::PATTERN_NUMBER, $project->getNumber() ?? '', $name);
|
$name = str_replace(self::PATTERN_NUMBER, $project->getNumber() ?? '', $name);
|
||||||
$name = str_replace(self::PATTERN_COMMENT, $project->getComment() ?? '', $name);
|
$name = str_replace(self::PATTERN_COMMENT, $project->getComment() ?? '', $name);
|
||||||
$name = str_replace(self::PATTERN_CUSTOMER, $project->getCustomer()?->getName() ?? '', $name);
|
$name = str_replace(self::PATTERN_CUSTOMER, $project->getCustomer()?->getName() ?? '', $name);
|
||||||
@@ -115,7 +115,7 @@ final class ProjectHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($name === '' || $name === self::SPACER) {
|
if ($name === '' || $name === self::SPACER) {
|
||||||
$name = $project->getName();
|
$name = $project->getName() ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return substr($name, 0, 110);
|
return substr($name, 0, 110);
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
|||||||
*/
|
*/
|
||||||
final class ProjectType extends AbstractType
|
final class ProjectType extends AbstractType
|
||||||
{
|
{
|
||||||
public function __construct(private ProjectHelper $projectHelper, private CustomerHelper $customerHelper)
|
public function __construct(
|
||||||
|
private readonly ProjectHelper $projectHelper,
|
||||||
|
private readonly CustomerHelper $customerHelper
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class BudgetStatisticModel implements BudgetStatisticModelInterface
|
|||||||
private ?BudgetStatistic $statistic = null;
|
private ?BudgetStatistic $statistic = null;
|
||||||
private ?BudgetStatistic $statisticTotal = null;
|
private ?BudgetStatistic $statisticTotal = null;
|
||||||
|
|
||||||
public function __construct(private EntityWithBudget $entity)
|
public function __construct(private readonly EntityWithBudget $entity)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class BudgetStatisticModel implements BudgetStatisticModelInterface
|
|||||||
return $this->statistic;
|
return $this->statistic;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setStatistic(BudgetStatistic $statistic)
|
public function setStatistic(BudgetStatistic $statistic): void
|
||||||
{
|
{
|
||||||
$this->statistic = $statistic;
|
$this->statistic = $statistic;
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ class BudgetStatisticModel implements BudgetStatisticModelInterface
|
|||||||
return $this->statisticTotal;
|
return $this->statisticTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setStatisticTotal(BudgetStatistic $statistic)
|
public function setStatisticTotal(BudgetStatistic $statistic): void
|
||||||
{
|
{
|
||||||
$this->statisticTotal = $statistic;
|
$this->statisticTotal = $statistic;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ use App\Reporting\ProjectInactive\ProjectInactiveQuery;
|
|||||||
use App\Reporting\ProjectView\ProjectViewModel;
|
use App\Reporting\ProjectView\ProjectViewModel;
|
||||||
use App\Reporting\ProjectView\ProjectViewQuery;
|
use App\Reporting\ProjectView\ProjectViewQuery;
|
||||||
use App\Repository\ActivityRepository;
|
use App\Repository\ActivityRepository;
|
||||||
use App\Repository\Loader\ProjectLoader;
|
|
||||||
use App\Repository\ProjectRepository;
|
use App\Repository\ProjectRepository;
|
||||||
use App\Repository\TimesheetRepository;
|
use App\Repository\TimesheetRepository;
|
||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
@@ -85,7 +84,7 @@ class ProjectStatisticService
|
|||||||
|
|
||||||
$qb = $this->projectRepository->createQueryBuilder('p');
|
$qb = $this->projectRepository->createQueryBuilder('p');
|
||||||
$qb
|
$qb
|
||||||
->select('p, c')
|
->select('p')
|
||||||
->leftJoin('p.customer', 'c')
|
->leftJoin('p.customer', 'c')
|
||||||
->andWhere($qb->expr()->eq('p.visible', true))
|
->andWhere($qb->expr()->eq('p.visible', true))
|
||||||
->andWhere($qb->expr()->eq('c.visible', true))
|
->andWhere($qb->expr()->eq('c.visible', true))
|
||||||
@@ -108,15 +107,9 @@ class ProjectStatisticService
|
|||||||
;
|
;
|
||||||
|
|
||||||
$this->projectRepository->addPermissionCriteria($qb, $user);
|
$this->projectRepository->addPermissionCriteria($qb, $user);
|
||||||
|
$query = $this->projectRepository->prepareProjectQuery($qb->getQuery());
|
||||||
|
|
||||||
/** @var Project[] $projects */
|
return $this->projectRepository->getProjects($query);
|
||||||
$projects = $qb->getQuery()->getResult();
|
|
||||||
|
|
||||||
// pre-cache customer objects instead of joining them
|
|
||||||
$loader = new ProjectLoader($this->projectRepository->createQueryBuilder('p')->getEntityManager(), false, false, false);
|
|
||||||
$loader->loadResults($projects);
|
|
||||||
|
|
||||||
return $projects;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -128,6 +121,10 @@ class ProjectStatisticService
|
|||||||
$begin = $dateRange->getBegin();
|
$begin = $dateRange->getBegin();
|
||||||
$end = $dateRange->getEnd();
|
$end = $dateRange->getEnd();
|
||||||
|
|
||||||
|
if ($begin === null || $end === null) {
|
||||||
|
throw new \InvalidArgumentException('Missing date in date-range for project statistics');
|
||||||
|
}
|
||||||
|
|
||||||
$qb = $this->projectRepository->createQueryBuilder('p');
|
$qb = $this->projectRepository->createQueryBuilder('p');
|
||||||
$qb
|
$qb
|
||||||
->select('p')
|
->select('p')
|
||||||
@@ -191,15 +188,9 @@ class ProjectStatisticService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->projectRepository->addPermissionCriteria($qb, $user);
|
$this->projectRepository->addPermissionCriteria($qb, $user);
|
||||||
|
$query = $this->projectRepository->prepareProjectQuery($qb->getQuery());
|
||||||
|
|
||||||
/** @var Project[] $projects */
|
return $this->projectRepository->getProjects($query);
|
||||||
$projects = $qb->getQuery()->getResult();
|
|
||||||
|
|
||||||
// pre-cache customer objects instead of joining them
|
|
||||||
$loader = new ProjectLoader($this->projectRepository->createQueryBuilder('p')->getEntityManager(), false, false, false);
|
|
||||||
$loader->loadResults($projects);
|
|
||||||
|
|
||||||
return $projects;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBudgetStatisticModel(Project $project, DateTimeInterface $today): ProjectBudgetStatisticModel
|
public function getBudgetStatisticModel(Project $project, DateTimeInterface $today): ProjectBudgetStatisticModel
|
||||||
@@ -687,15 +678,9 @@ class ProjectStatisticService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->projectRepository->addPermissionCriteria($qb, $user);
|
$this->projectRepository->addPermissionCriteria($qb, $user);
|
||||||
|
$query = $this->projectRepository->prepareProjectQuery($qb->getQuery());
|
||||||
|
|
||||||
/** @var Project[] $projects */
|
return $this->projectRepository->getProjects($query);
|
||||||
$projects = $qb->getQuery()->getResult();
|
|
||||||
|
|
||||||
// pre-cache customer objects instead of joining them
|
|
||||||
$loader = new ProjectLoader($this->projectRepository->createQueryBuilder('p')->getEntityManager(), false, false, false);
|
|
||||||
$loader->loadResults($projects);
|
|
||||||
|
|
||||||
return $projects;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,14 +16,16 @@ use App\Entity\Team;
|
|||||||
use App\Entity\Timesheet;
|
use App\Entity\Timesheet;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Repository\Loader\ActivityLoader;
|
use App\Repository\Loader\ActivityLoader;
|
||||||
use App\Repository\Paginator\LoaderPaginator;
|
use App\Repository\Paginator\LoaderQueryPaginator;
|
||||||
use App\Repository\Paginator\PaginatorInterface;
|
use App\Repository\Paginator\PaginatorInterface;
|
||||||
use App\Repository\Query\ActivityFormTypeQuery;
|
use App\Repository\Query\ActivityFormTypeQuery;
|
||||||
use App\Repository\Query\ActivityQuery;
|
use App\Repository\Query\ActivityQuery;
|
||||||
|
use App\Repository\Query\ActivityQueryHydrate;
|
||||||
use App\Utils\Pagination;
|
use App\Utils\Pagination;
|
||||||
use Doctrine\DBAL\ParameterType;
|
use Doctrine\DBAL\ParameterType;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\ORM\Exception\ORMException;
|
use Doctrine\ORM\Exception\ORMException;
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\Query\Expr\Andx;
|
use Doctrine\ORM\Query\Expr\Andx;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
@@ -37,53 +39,53 @@ class ActivityRepository extends EntityRepository
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Project $project
|
* @param Project $project
|
||||||
* @return Activity[]
|
* @return array<Activity>
|
||||||
*/
|
*/
|
||||||
public function findByProject(Project $project): array
|
public function findByProject(Project $project): array
|
||||||
{
|
{
|
||||||
return $this->findBy(['project' => $project]);
|
$query = new ActivityQuery();
|
||||||
|
$query->addProject($project);
|
||||||
|
|
||||||
|
return $this->getActivitiesForQuery($query);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int[] $activityIds
|
* @param int[] $activityIds
|
||||||
* @return Activity[]
|
* @return array<Activity>
|
||||||
*/
|
*/
|
||||||
public function findByIds(array $activityIds): array
|
public function findByIds(array $activityIds): array
|
||||||
{
|
{
|
||||||
|
$ids = array_filter(
|
||||||
|
array_unique($activityIds),
|
||||||
|
function ($value) {
|
||||||
|
return $value > 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (\count($ids) === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
$qb = $this->createQueryBuilder('a');
|
$qb = $this->createQueryBuilder('a');
|
||||||
$qb
|
$qb
|
||||||
->where($qb->expr()->in('a.id', ':id'))
|
->where($qb->expr()->in('a.id', ':id'))
|
||||||
->setParameter('id', $activityIds)
|
->setParameter('id', $ids)
|
||||||
;
|
;
|
||||||
|
|
||||||
$activities = $qb->getQuery()->getResult();
|
return $this->getActivities($this->prepareActivityQuery($qb->getQuery()));
|
||||||
|
|
||||||
$loader = new ActivityLoader($qb->getEntityManager(), true);
|
|
||||||
$loader->loadResults($activities);
|
|
||||||
|
|
||||||
return $activities;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function saveActivity(Activity $activity): void
|
||||||
* @param Activity $activity
|
|
||||||
* @throws ORMException
|
|
||||||
* @throws \Doctrine\ORM\OptimisticLockException
|
|
||||||
*/
|
|
||||||
public function saveActivity(Activity $activity)
|
|
||||||
{
|
{
|
||||||
$entityManager = $this->getEntityManager();
|
$entityManager = $this->getEntityManager();
|
||||||
$entityManager->persist($activity);
|
$entityManager->persist($activity);
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function countActivity(?bool $visible = null): int
|
||||||
* @param null|bool $visible
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function countActivity($visible = null): int
|
|
||||||
{
|
{
|
||||||
if (null !== $visible) {
|
if (null !== $visible) {
|
||||||
return $this->count(['visible' => (bool) $visible]);
|
return $this->count(['visible' => $visible]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->count([]);
|
return $this->count([]);
|
||||||
@@ -157,8 +159,7 @@ class ActivityRepository extends EntityRepository
|
|||||||
/**
|
/**
|
||||||
* Returns a query builder that is used for ActivityType and your own 'query_builder' option.
|
* Returns a query builder that is used for ActivityType and your own 'query_builder' option.
|
||||||
*
|
*
|
||||||
* @param ActivityFormTypeQuery $query
|
* @internal
|
||||||
* @return QueryBuilder
|
|
||||||
*/
|
*/
|
||||||
public function getQueryBuilderForFormType(ActivityFormTypeQuery $query): QueryBuilder
|
public function getQueryBuilderForFormType(ActivityFormTypeQuery $query): QueryBuilder
|
||||||
{
|
{
|
||||||
@@ -252,15 +253,17 @@ class ActivityRepository extends EntityRepository
|
|||||||
|
|
||||||
private function getQueryBuilderForQuery(ActivityQuery $query): QueryBuilder
|
private function getQueryBuilderForQuery(ActivityQuery $query): QueryBuilder
|
||||||
{
|
{
|
||||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
$qb = $this->createQueryBuilder('a');
|
||||||
|
|
||||||
$qb
|
$qb
|
||||||
->select('a')
|
|
||||||
->from(Activity::class, 'a')
|
|
||||||
->leftJoin('a.project', 'p')
|
->leftJoin('a.project', 'p')
|
||||||
->leftJoin('p.customer', 'c')
|
->leftJoin('p.customer', 'c')
|
||||||
;
|
;
|
||||||
|
|
||||||
|
if (\count($query->getActivityIds()) > 0) {
|
||||||
|
$qb->andWhere($qb->expr()->in('a.id', ':id'))->setParameter('id', $query->getActivityIds());
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($query->getOrderGroups() as $orderBy => $order) {
|
foreach ($query->getOrderGroups() as $orderBy => $order) {
|
||||||
switch ($orderBy) {
|
switch ($orderBy) {
|
||||||
case 'project':
|
case 'project':
|
||||||
@@ -356,6 +359,9 @@ class ActivityRepository extends EntityRepository
|
|||||||
return ['a.name', 'a.comment', 'a.number'];
|
return ['a.name', 'a.comment', 'a.number'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int<0, max>
|
||||||
|
*/
|
||||||
public function countActivitiesForQuery(ActivityQuery $query): int
|
public function countActivitiesForQuery(ActivityQuery $query): int
|
||||||
{
|
{
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$qb = $this->getQueryBuilderForQuery($query);
|
||||||
@@ -366,7 +372,7 @@ class ActivityRepository extends EntityRepository
|
|||||||
->select($qb->expr()->countDistinct('a.id'))
|
->select($qb->expr()->countDistinct('a.id'))
|
||||||
;
|
;
|
||||||
|
|
||||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
return (int) $qb->getQuery()->getSingleScalarResult(); // @phpstan-ignore-line
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPagerfantaForQuery(ActivityQuery $query): Pagination
|
public function getPagerfantaForQuery(ActivityQuery $query): Pagination
|
||||||
@@ -374,33 +380,41 @@ class ActivityRepository extends EntityRepository
|
|||||||
return new Pagination($this->getPaginatorForQuery($query), $query);
|
return new Pagination($this->getPaginatorForQuery($query), $query);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPaginatorForQuery(ActivityQuery $query): PaginatorInterface
|
/**
|
||||||
|
* @return PaginatorInterface<Activity>
|
||||||
|
*/
|
||||||
|
private function getPaginatorForQuery(ActivityQuery $activityQuery): PaginatorInterface
|
||||||
{
|
{
|
||||||
$counter = $this->countActivitiesForQuery($query);
|
$counter = $this->countActivitiesForQuery($activityQuery);
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$query = $this->createActivityQuery($activityQuery);
|
||||||
|
|
||||||
return new LoaderPaginator(new ActivityLoader($qb->getEntityManager()), $qb, $counter);
|
return new LoaderQueryPaginator(new ActivityLoader($this->getEntityManager()), $query, $counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ActivityQuery $query
|
|
||||||
* @return Activity[]
|
* @return Activity[]
|
||||||
*/
|
*/
|
||||||
public function getActivitiesForQuery(ActivityQuery $query): iterable
|
public function getActivitiesForQuery(ActivityQuery $query): array
|
||||||
{
|
{
|
||||||
// this is using the paginator internally, as it will load all joined entities into the working unit
|
return $this->getActivities($this->createActivityQuery($query));
|
||||||
// do not "optimize" to use the query directly, as it would results in hundreds of additional lazy queries
|
|
||||||
$paginator = $this->getPaginatorForQuery($query);
|
|
||||||
|
|
||||||
return $paginator->getAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Activity $delete
|
* @param Query<Activity> $query
|
||||||
* @param Activity|null $replace
|
* @return Activity[]
|
||||||
* @throws \Doctrine\ORM\Exception\ORMException
|
|
||||||
*/
|
*/
|
||||||
public function deleteActivity(Activity $delete, ?Activity $replace = null)
|
public function getActivities(Query $query): array
|
||||||
|
{
|
||||||
|
/** @var array<Activity> $activities */
|
||||||
|
$activities = $query->execute();
|
||||||
|
|
||||||
|
$loader = new ActivityLoader($this->getEntityManager());
|
||||||
|
$loader->loadResults($activities);
|
||||||
|
|
||||||
|
return $activities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteActivity(Activity $delete, ?Activity $replace = null): void
|
||||||
{
|
{
|
||||||
$em = $this->getEntityManager();
|
$em = $this->getEntityManager();
|
||||||
$em->beginTransaction();
|
$em->beginTransaction();
|
||||||
@@ -425,4 +439,47 @@ class ActivityRepository extends EntityRepository
|
|||||||
throw $ex;
|
throw $ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Query<Activity>
|
||||||
|
*/
|
||||||
|
private function createActivityQuery(ActivityQuery $activityQuery): Query
|
||||||
|
{
|
||||||
|
$query = $this->getQueryBuilderForQuery($activityQuery)->getQuery();
|
||||||
|
$query = $this->prepareActivityQuery($query);
|
||||||
|
|
||||||
|
foreach ($activityQuery->getHydrate() as $hydrate) {
|
||||||
|
switch ($hydrate) {
|
||||||
|
case ActivityQueryHydrate::TEAMS:
|
||||||
|
// does not yet work, see https://github.com/doctrine/orm/pull/8391
|
||||||
|
// $query->setFetchMode(Activity::class, 'teams', ClassMetadata::FETCH_EAGER);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ActivityQueryHydrate::TEAM_MEMBER:
|
||||||
|
// does not yet work, see https://github.com/doctrine/orm/issues/11254
|
||||||
|
// $query->setFetchMode(Activity::class, 'teams', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(Team::class, 'members', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(TeamMember::class, 'user', ClassMetadata::FETCH_EAGER);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Query<Activity> $query
|
||||||
|
* @return Query<Activity>
|
||||||
|
*/
|
||||||
|
public function prepareActivityQuery(Query $query): Query
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->getConfiguration()->setEagerFetchBatchSize(300);
|
||||||
|
|
||||||
|
$query->setFetchMode(Activity::class, 'meta', ClassMetadata::FETCH_EAGER);
|
||||||
|
$query->setFetchMode(Activity::class, 'project', ClassMetadata::FETCH_EAGER);
|
||||||
|
|
||||||
|
// $query->setFetchMode(Project::class, 'customer', ClassMetadata::FETCH_EAGER);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,16 @@ use App\Entity\Project;
|
|||||||
use App\Entity\Team;
|
use App\Entity\Team;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Repository\Loader\CustomerLoader;
|
use App\Repository\Loader\CustomerLoader;
|
||||||
use App\Repository\Paginator\LoaderPaginator;
|
use App\Repository\Paginator\LoaderQueryPaginator;
|
||||||
use App\Repository\Paginator\PaginatorInterface;
|
use App\Repository\Paginator\PaginatorInterface;
|
||||||
use App\Repository\Query\CustomerFormTypeQuery;
|
use App\Repository\Query\CustomerFormTypeQuery;
|
||||||
use App\Repository\Query\CustomerQuery;
|
use App\Repository\Query\CustomerQuery;
|
||||||
|
use App\Repository\Query\CustomerQueryHydrate;
|
||||||
use App\Utils\Pagination;
|
use App\Utils\Pagination;
|
||||||
use Doctrine\DBAL\ParameterType;
|
use Doctrine\DBAL\ParameterType;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\ORM\Exception\ORMException;
|
use Doctrine\ORM\Exception\ORMException;
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\Query\Expr\Andx;
|
use Doctrine\ORM\Query\Expr\Andx;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
@@ -37,22 +39,28 @@ class CustomerRepository extends EntityRepository
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int[] $customerIDs
|
* @param int[] $customerIDs
|
||||||
* @return Customer[]
|
* @return array<Customer>
|
||||||
*/
|
*/
|
||||||
public function findByIds(array $customerIDs): array
|
public function findByIds(array $customerIDs): array
|
||||||
{
|
{
|
||||||
|
$ids = array_filter(
|
||||||
|
array_unique($customerIDs),
|
||||||
|
function ($value) {
|
||||||
|
return $value > 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (\count($ids) === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
$qb = $this->createQueryBuilder('c');
|
$qb = $this->createQueryBuilder('c');
|
||||||
$qb
|
$qb
|
||||||
->where($qb->expr()->in('c.id', ':id'))
|
->where($qb->expr()->in('c.id', ':id'))
|
||||||
->setParameter('id', $customerIDs)
|
->setParameter('id', $ids)
|
||||||
;
|
;
|
||||||
|
|
||||||
$customers = $qb->getQuery()->getResult();
|
return $this->getCustomers($this->prepareCustomerQuery($qb->getQuery()), new CustomerQuery());
|
||||||
|
|
||||||
$loader = new CustomerLoader($qb->getEntityManager(), true);
|
|
||||||
$loader->loadResults($customers);
|
|
||||||
|
|
||||||
return $customers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveCustomer(Customer $customer): void
|
public function saveCustomer(Customer $customer): void
|
||||||
@@ -120,6 +128,8 @@ class CustomerRepository extends EntityRepository
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a query builder that is used for CustomerType and your own 'query_builder' option.
|
* Returns a query builder that is used for CustomerType and your own 'query_builder' option.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
*/
|
*/
|
||||||
public function getQueryBuilderForFormType(CustomerFormTypeQuery $query): QueryBuilder
|
public function getQueryBuilderForFormType(CustomerFormTypeQuery $query): QueryBuilder
|
||||||
{
|
{
|
||||||
@@ -163,18 +173,14 @@ class CustomerRepository extends EntityRepository
|
|||||||
|
|
||||||
private function getQueryBuilderForQuery(CustomerQuery $query): QueryBuilder
|
private function getQueryBuilderForQuery(CustomerQuery $query): QueryBuilder
|
||||||
{
|
{
|
||||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
$qb = $this->createQueryBuilder('c');
|
||||||
|
|
||||||
$qb
|
if (\count($query->getCustomerIds()) > 0) {
|
||||||
->select('c')
|
$qb->andWhere($qb->expr()->in('c.id', ':id'))->setParameter('id', $query->getCustomerIds());
|
||||||
->from(Customer::class, 'c')
|
}
|
||||||
;
|
|
||||||
|
|
||||||
if ($query->getCountry() !== null) {
|
if ($query->getCountry() !== null) {
|
||||||
$qb
|
$qb->andWhere($qb->expr()->eq('c.country', ':country'))->setParameter('country', $query->getCountry());
|
||||||
->andWhere($qb->expr()->eq('c.country', ':country'))
|
|
||||||
->setParameter('country', $query->getCountry())
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($query->getOrderGroups() as $orderBy => $order) {
|
foreach ($query->getOrderGroups() as $orderBy => $order) {
|
||||||
@@ -190,11 +196,9 @@ class CustomerRepository extends EntityRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($query->isShowVisible()) {
|
if ($query->isShowVisible()) {
|
||||||
$qb->andWhere($qb->expr()->eq('c.visible', ':visible'));
|
$qb->andWhere($qb->expr()->eq('c.visible', ':visible'))->setParameter('visible', true, ParameterType::BOOLEAN);
|
||||||
$qb->setParameter('visible', true, ParameterType::BOOLEAN);
|
|
||||||
} elseif ($query->isShowHidden()) {
|
} elseif ($query->isShowHidden()) {
|
||||||
$qb->andWhere($qb->expr()->eq('c.visible', ':visible'));
|
$qb->andWhere($qb->expr()->eq('c.visible', ':visible'))->setParameter('visible', false, ParameterType::BOOLEAN);
|
||||||
$qb->setParameter('visible', false, ParameterType::BOOLEAN);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->addPermissionCriteria($qb, $query->getCurrentUser(), $query->getTeams());
|
$this->addPermissionCriteria($qb, $query->getCurrentUser(), $query->getTeams());
|
||||||
@@ -227,6 +231,10 @@ class CustomerRepository extends EntityRepository
|
|||||||
return new Pagination($this->getPaginatorForQuery($query), $query);
|
return new Pagination($this->getPaginatorForQuery($query), $query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FIXME make this private and remove the widget that this currently uses
|
||||||
|
* @return int<0, max>
|
||||||
|
*/
|
||||||
public function countCustomersForQuery(CustomerQuery $query): int
|
public function countCustomersForQuery(CustomerQuery $query): int
|
||||||
{
|
{
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$qb = $this->getQueryBuilderForQuery($query);
|
||||||
@@ -237,27 +245,81 @@ class CustomerRepository extends EntityRepository
|
|||||||
->select($qb->expr()->countDistinct('c.id'))
|
->select($qb->expr()->countDistinct('c.id'))
|
||||||
;
|
;
|
||||||
|
|
||||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
return (int) $qb->getQuery()->getSingleScalarResult(); // @phpstan-ignore-line
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPaginatorForQuery(CustomerQuery $query): PaginatorInterface
|
/**
|
||||||
|
* @return PaginatorInterface<Customer>
|
||||||
|
*/
|
||||||
|
private function getPaginatorForQuery(CustomerQuery $customerQuery): PaginatorInterface
|
||||||
{
|
{
|
||||||
$counter = $this->countCustomersForQuery($query);
|
$counter = $this->countCustomersForQuery($customerQuery);
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$query = $this->createCustomerQuery($customerQuery);
|
||||||
|
|
||||||
return new LoaderPaginator(new CustomerLoader($qb->getEntityManager()), $qb, $counter);
|
return new LoaderQueryPaginator(new CustomerLoader($this->getEntityManager(), $customerQuery), $query, $counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Query<Customer>
|
||||||
|
*/
|
||||||
|
private function createCustomerQuery(CustomerQuery $customerQuery): Query
|
||||||
|
{
|
||||||
|
$query = $this->getQueryBuilderForQuery($customerQuery)->getQuery();
|
||||||
|
$query = $this->prepareCustomerQuery($query);
|
||||||
|
|
||||||
|
foreach ($customerQuery->getHydrate() as $hydrate) {
|
||||||
|
switch ($hydrate) {
|
||||||
|
case CustomerQueryHydrate::TEAMS:
|
||||||
|
// does not yet work, see https://github.com/doctrine/orm/pull/8391
|
||||||
|
// $query->setFetchMode(Customer::class, 'teams', ClassMetadata::FETCH_EAGER);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CustomerQueryHydrate::TEAM_MEMBER:
|
||||||
|
// does not yet work, see https://github.com/doctrine/orm/issues/11254
|
||||||
|
// $query->setFetchMode(Customer::class, 'teams', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(Team::class, 'members', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(TeamMember::class, 'user', ClassMetadata::FETCH_EAGER);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Query<Customer> $query
|
||||||
|
* @return Query<Customer>
|
||||||
|
*/
|
||||||
|
public function prepareCustomerQuery(Query $query): Query
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->getConfiguration()->setEagerFetchBatchSize(300);
|
||||||
|
|
||||||
|
$query->setFetchMode(Customer::class, 'meta', ClassMetadata::FETCH_EAGER);
|
||||||
|
|
||||||
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Customer[]
|
* @return Customer[]
|
||||||
*/
|
*/
|
||||||
public function getCustomersForQuery(CustomerQuery $query): iterable
|
public function getCustomersForQuery(CustomerQuery $customerQuery): array
|
||||||
{
|
{
|
||||||
// this is using the paginator internally, as it will load all joined entities into the working unit
|
return $this->getCustomers($this->createCustomerQuery($customerQuery), $customerQuery);
|
||||||
// do not "optimize" to use the query directly, as it would results in hundreds of additional lazy queries
|
}
|
||||||
$paginator = $this->getPaginatorForQuery($query);
|
|
||||||
|
|
||||||
return $paginator->getAll();
|
/**
|
||||||
|
* @param Query<Customer> $query
|
||||||
|
* @return Customer[]
|
||||||
|
*/
|
||||||
|
public function getCustomers(Query $query, CustomerQuery $customerQuery): array
|
||||||
|
{
|
||||||
|
/** @var array<Customer> $customers */
|
||||||
|
$customers = $query->execute();
|
||||||
|
|
||||||
|
$loader = new CustomerLoader($this->getEntityManager(), $customerQuery);
|
||||||
|
$loader->loadResults($customers);
|
||||||
|
|
||||||
|
return $customers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteCustomer(Customer $delete, ?Customer $replace = null): void
|
public function deleteCustomer(Customer $delete, ?Customer $replace = null): void
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ use App\Entity\Invoice;
|
|||||||
use App\Entity\InvoiceMeta;
|
use App\Entity\InvoiceMeta;
|
||||||
use App\Entity\Team;
|
use App\Entity\Team;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Repository\Loader\InvoiceLoader;
|
|
||||||
use App\Repository\Paginator\LoaderPaginator;
|
|
||||||
use App\Repository\Paginator\PaginatorInterface;
|
use App\Repository\Paginator\PaginatorInterface;
|
||||||
|
use App\Repository\Paginator\QueryPaginator;
|
||||||
use App\Repository\Query\InvoiceArchiveQuery;
|
use App\Repository\Query\InvoiceArchiveQuery;
|
||||||
use App\Utils\Pagination;
|
use App\Utils\Pagination;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -254,7 +255,10 @@ class InvoiceRepository extends EntityRepository
|
|||||||
return ['i.comment', 'customer.name', 'customer.company'];
|
return ['i.comment', 'customer.name', 'customer.company'];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function countInvoicesForQuery(InvoiceArchiveQuery $query): int
|
/**
|
||||||
|
* @return int<0, max>
|
||||||
|
*/
|
||||||
|
private function countInvoicesForQuery(InvoiceArchiveQuery $query): int
|
||||||
{
|
{
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$qb = $this->getQueryBuilderForQuery($query);
|
||||||
$qb
|
$qb
|
||||||
@@ -264,7 +268,7 @@ class InvoiceRepository extends EntityRepository
|
|||||||
->select($qb->expr()->countDistinct('i.id'))
|
->select($qb->expr()->countDistinct('i.id'))
|
||||||
;
|
;
|
||||||
|
|
||||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
return (int) $qb->getQuery()->getSingleScalarResult(); // @phpstan-ignore-line
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -273,23 +277,38 @@ class InvoiceRepository extends EntityRepository
|
|||||||
*/
|
*/
|
||||||
public function getInvoicesForQuery(InvoiceArchiveQuery $query): iterable
|
public function getInvoicesForQuery(InvoiceArchiveQuery $query): iterable
|
||||||
{
|
{
|
||||||
// this is using the paginator internally, as it will load all joined entities into the working unit
|
return $this->createInvoiceQuery($query)->execute(); // @phpstan-ignore-line
|
||||||
// do not "optimize" to use the query directly, as it would results in hundreds of additional lazy queries
|
|
||||||
$paginator = $this->getPaginatorForQuery($query);
|
|
||||||
|
|
||||||
return $paginator->getAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPaginatorForQuery(InvoiceArchiveQuery $query): PaginatorInterface
|
/**
|
||||||
|
* @return PaginatorInterface<Invoice>
|
||||||
|
*/
|
||||||
|
private function getPaginatorForQuery(InvoiceArchiveQuery $query): PaginatorInterface
|
||||||
{
|
{
|
||||||
$counter = $this->countInvoicesForQuery($query);
|
$counter = $this->countInvoicesForQuery($query);
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$query = $this->createInvoiceQuery($query);
|
||||||
|
|
||||||
return new LoaderPaginator(new InvoiceLoader($qb->getEntityManager()), $qb, $counter);
|
return new QueryPaginator($query, $counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPagerfantaForQuery(InvoiceArchiveQuery $query): Pagination
|
public function getPagerfantaForQuery(InvoiceArchiveQuery $query): Pagination
|
||||||
{
|
{
|
||||||
return new Pagination($this->getPaginatorForQuery($query), $query);
|
return new Pagination($this->getPaginatorForQuery($query), $query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Query<Invoice>
|
||||||
|
*/
|
||||||
|
private function createInvoiceQuery(InvoiceArchiveQuery $invoiceArchiveQuery): Query
|
||||||
|
{
|
||||||
|
$query = $this->getQueryBuilderForQuery($invoiceArchiveQuery)->getQuery();
|
||||||
|
|
||||||
|
$this->getEntityManager()->getConfiguration()->setEagerFetchBatchSize(300);
|
||||||
|
|
||||||
|
$query->setFetchMode(Invoice::class, 'meta', ClassMetadata::FETCH_EAGER);
|
||||||
|
$query->setFetchMode(Invoice::class, 'user', ClassMetadata::FETCH_EAGER);
|
||||||
|
$query->setFetchMode(Invoice::class, 'customer', ClassMetadata::FETCH_EAGER);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ namespace App\Repository;
|
|||||||
|
|
||||||
use App\Entity\InvoiceTemplate;
|
use App\Entity\InvoiceTemplate;
|
||||||
use App\Repository\Paginator\PaginatorInterface;
|
use App\Repository\Paginator\PaginatorInterface;
|
||||||
use App\Repository\Paginator\QueryBuilderPaginator;
|
use App\Repository\Paginator\QueryPaginator;
|
||||||
use App\Repository\Query\BaseQuery;
|
use App\Repository\Query\BaseQuery;
|
||||||
use App\Utils\Pagination;
|
use App\Utils\Pagination;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,12 +50,16 @@ class InvoiceTemplateRepository extends EntityRepository
|
|||||||
return $qb;
|
return $qb;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPaginatorForQuery(BaseQuery $query): PaginatorInterface
|
/**
|
||||||
|
* @return PaginatorInterface<InvoiceTemplate>
|
||||||
|
*/
|
||||||
|
private function getPaginatorForQuery(BaseQuery $baseQuery): PaginatorInterface
|
||||||
{
|
{
|
||||||
$counter = $this->countTemplatesForQuery($query);
|
$counter = $this->countTemplatesForQuery($baseQuery);
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
/** @var Query<InvoiceTemplate> $query */
|
||||||
|
$query = $this->getQueryBuilderForQuery($baseQuery)->getQuery();
|
||||||
|
|
||||||
return new QueryBuilderPaginator($qb, $counter);
|
return new QueryPaginator($query, $counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPagerfantaForQuery(BaseQuery $query): Pagination
|
public function getPagerfantaForQuery(BaseQuery $query): Pagination
|
||||||
@@ -62,6 +67,9 @@ class InvoiceTemplateRepository extends EntityRepository
|
|||||||
return new Pagination($this->getPaginatorForQuery($query), $query);
|
return new Pagination($this->getPaginatorForQuery($query), $query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int<0, max>
|
||||||
|
*/
|
||||||
public function countTemplatesForQuery(BaseQuery $query): int
|
public function countTemplatesForQuery(BaseQuery $query): int
|
||||||
{
|
{
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$qb = $this->getQueryBuilderForQuery($query);
|
||||||
@@ -71,7 +79,7 @@ class InvoiceTemplateRepository extends EntityRepository
|
|||||||
->select($qb->expr()->countDistinct('t.id'))
|
->select($qb->expr()->countDistinct('t.id'))
|
||||||
;
|
;
|
||||||
|
|
||||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
return (int) $qb->getQuery()->getSingleScalarResult(); // @phpstan-ignore-line
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveTemplate(InvoiceTemplate $template): void
|
public function saveTemplate(InvoiceTemplate $template): void
|
||||||
|
|||||||
@@ -12,65 +12,44 @@ namespace App\Repository\Loader;
|
|||||||
use App\Entity\Activity;
|
use App\Entity\Activity;
|
||||||
use App\Entity\Customer;
|
use App\Entity\Customer;
|
||||||
use App\Entity\Project;
|
use App\Entity\Project;
|
||||||
use App\Entity\Team;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @implements LoaderInterface<Activity>
|
||||||
|
*/
|
||||||
final class ActivityLoader implements LoaderInterface
|
final class ActivityLoader implements LoaderInterface
|
||||||
{
|
{
|
||||||
public function __construct(private EntityManagerInterface $entityManager, private bool $fullyHydrated = false)
|
public function __construct(private readonly EntityManagerInterface $entityManager)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int|Activity> $results
|
* @param array<Activity> $results
|
||||||
*/
|
*/
|
||||||
public function loadResults(array $results): void
|
public function loadResults(array $results): void
|
||||||
{
|
{
|
||||||
if (empty($results)) {
|
if (\count($results) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$ids = array_map(function ($activity) {
|
$activityIds = array_filter(array_unique(array_map(function (Activity $activity) {
|
||||||
if ($activity instanceof Activity) {
|
|
||||||
// make sure that this potential doctrine proxy is initialized and filled with all data
|
// make sure that this potential doctrine proxy is initialized and filled with all data
|
||||||
$activity->getName();
|
$activity->getName();
|
||||||
|
// using reporting controller tests will show that error
|
||||||
|
$activity->getProject()?->getName();
|
||||||
|
|
||||||
return $activity->getId();
|
return $activity->getId();
|
||||||
}
|
}, $results)), function ($value) { return $value !== null; });
|
||||||
|
|
||||||
return $activity;
|
|
||||||
}, $results);
|
|
||||||
|
|
||||||
$em = $this->entityManager;
|
$em = $this->entityManager;
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
$projectIds = array_filter(array_unique(array_map(function (Activity $activity) {
|
||||||
/** @var Activity[] $activities */
|
return $activity->getProject()?->getId();
|
||||||
$activities = $qb->select('PARTIAL a.{id}', 'project')
|
}, $results)), function ($value) { return $value !== null; });
|
||||||
->from(Activity::class, 'a')
|
|
||||||
->leftJoin('a.project', 'project')
|
|
||||||
->andWhere($qb->expr()->isNotNull('a.project'))
|
|
||||||
->andWhere($qb->expr()->in('a.id', $ids))
|
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
|
||||||
$qb->select('PARTIAL a.{id}', 'meta')
|
|
||||||
->from(Activity::class, 'a')
|
|
||||||
->leftJoin('a.meta', 'meta')
|
|
||||||
->andWhere($qb->expr()->in('a.id', $ids))
|
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
// global activities don't have projects
|
// global activities don't have projects
|
||||||
if (!empty($activities)) {
|
if (\count($projectIds) > 0) {
|
||||||
$projectIds = array_unique(array_map(function (Activity $activity) {
|
|
||||||
if (null === $activity->getProject()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $activity->getProject()->getId();
|
|
||||||
}, $activities));
|
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
$qb = $em->createQueryBuilder();
|
||||||
$qb->select('PARTIAL project.{id}', 'customer')
|
$qb->select('PARTIAL project.{id}', 'customer')
|
||||||
->from(Project::class, 'project')
|
->from(Project::class, 'project')
|
||||||
@@ -79,14 +58,6 @@ final class ActivityLoader implements LoaderInterface
|
|||||||
->getQuery()
|
->getQuery()
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
$customerIds = array_unique(array_map(function (Activity $activity) {
|
|
||||||
if (null === $activity->getProject()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $activity->getProject()->getCustomer()->getId();
|
|
||||||
}, $activities));
|
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
$qb = $em->createQueryBuilder();
|
||||||
$qb->select('PARTIAL project.{id}', 'teams')
|
$qb->select('PARTIAL project.{id}', 'teams')
|
||||||
->from(Project::class, 'project')
|
->from(Project::class, 'project')
|
||||||
@@ -95,6 +66,11 @@ final class ActivityLoader implements LoaderInterface
|
|||||||
->getQuery()
|
->getQuery()
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
|
$customerIds = array_filter(array_unique(array_map(function (Activity $activity) {
|
||||||
|
return $activity->getProject()?->getCustomer()?->getId();
|
||||||
|
}, $results)), function ($value) { return $value !== null; });
|
||||||
|
|
||||||
|
if (\count($customerIds) > 0) {
|
||||||
$qb = $em->createQueryBuilder();
|
$qb = $em->createQueryBuilder();
|
||||||
$qb->select('PARTIAL customer.{id}', 'teams')
|
$qb->select('PARTIAL customer.{id}', 'teams')
|
||||||
->from(Customer::class, 'customer')
|
->from(Customer::class, 'customer')
|
||||||
@@ -103,36 +79,15 @@ final class ActivityLoader implements LoaderInterface
|
|||||||
->getQuery()
|
->getQuery()
|
||||||
->execute();
|
->execute();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// required on "Activity listing" page for non super-admins
|
||||||
$qb = $em->createQueryBuilder();
|
$qb = $em->createQueryBuilder();
|
||||||
$qb->select('PARTIAL a.{id}', 'teams')
|
$qb->select('PARTIAL a.{id}', 'teams')
|
||||||
->from(Activity::class, 'a')
|
->from(Activity::class, 'a')
|
||||||
->leftJoin('a.teams', 'teams')
|
->leftJoin('a.teams', 'teams')
|
||||||
->andWhere($qb->expr()->in('a.id', $ids))
|
->andWhere($qb->expr()->in('a.id', $activityIds))
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
// do not load team members or leads by default, because they will only be used on detail pages
|
|
||||||
// and there is no benefit in adding multiple queries for most requests when they are only needed in one place
|
|
||||||
if ($this->fullyHydrated) {
|
|
||||||
$teamIds = [];
|
|
||||||
foreach ($activities as $activity) {
|
|
||||||
foreach ($activity->getTeams() as $team) {
|
|
||||||
$teamIds[] = $team->getId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$teamIds = array_unique($teamIds);
|
|
||||||
|
|
||||||
if (\count($teamIds) > 0) {
|
|
||||||
$qb = $em->createQueryBuilder();
|
|
||||||
$qb->select('PARTIAL team.{id}', 'members', 'user')
|
|
||||||
->from(Team::class, 'team')
|
|
||||||
->leftJoin('team.members', 'members')
|
|
||||||
->leftJoin('members.user', 'user')
|
|
||||||
->andWhere($qb->expr()->in('team.id', $teamIds))
|
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->execute();
|
->execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,58 +11,73 @@ namespace App\Repository\Loader;
|
|||||||
|
|
||||||
use App\Entity\Customer;
|
use App\Entity\Customer;
|
||||||
use App\Entity\Team;
|
use App\Entity\Team;
|
||||||
|
use App\Repository\Query\CustomerQuery;
|
||||||
|
use App\Repository\Query\CustomerQueryHydrate;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @implements LoaderInterface<Customer>
|
||||||
|
*/
|
||||||
final class CustomerLoader implements LoaderInterface
|
final class CustomerLoader implements LoaderInterface
|
||||||
{
|
{
|
||||||
public function __construct(private EntityManagerInterface $entityManager, private bool $fullyHydrated = false)
|
public function __construct(
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly CustomerQuery $query
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int|Customer> $results
|
* @param array<Customer> $results
|
||||||
*/
|
*/
|
||||||
public function loadResults(array $results): void
|
public function loadResults(array $results): void
|
||||||
{
|
{
|
||||||
if (empty($results)) {
|
if (\count($results) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$ids = array_map(function ($customer) {
|
$customerIds = array_filter(array_unique(array_map(function (Customer $customer) {
|
||||||
if ($customer instanceof Customer) {
|
|
||||||
// make sure that this potential doctrine proxy is initialized and filled with all data
|
// make sure that this potential doctrine proxy is initialized and filled with all data
|
||||||
$customer->getName();
|
$customer->getName();
|
||||||
|
|
||||||
return $customer->getId();
|
return $customer->getId();
|
||||||
|
}, $results)), function ($value) { return $value !== null; });
|
||||||
|
|
||||||
|
$hydrateTeams = false;
|
||||||
|
$hydrateTeamMembers = false;
|
||||||
|
|
||||||
|
foreach ($this->query->getHydrate() as $hydrate) {
|
||||||
|
switch ($hydrate) {
|
||||||
|
case CustomerQueryHydrate::TEAMS:
|
||||||
|
$hydrateTeams = true;
|
||||||
|
break;
|
||||||
|
case CustomerQueryHydrate::TEAM_MEMBER:
|
||||||
|
$hydrateTeams = true;
|
||||||
|
$hydrateTeamMembers = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $customer;
|
if (!$hydrateTeams) {
|
||||||
}, $results);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$em = $this->entityManager;
|
$em = $this->entityManager;
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
// required where we need to check team permissions, e.g. "Customer listing"
|
||||||
/** @var Customer[] $customers */
|
|
||||||
$customers = $qb->select('PARTIAL c.{id}', 'meta')
|
|
||||||
->from(Customer::class, 'c')
|
|
||||||
->leftJoin('c.meta', 'meta')
|
|
||||||
->andWhere($qb->expr()->in('c.id', $ids))
|
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
$qb = $em->createQueryBuilder();
|
||||||
$qb->select('PARTIAL c.{id}', 'teams')
|
$qb->select('PARTIAL c.{id}', 'teams')
|
||||||
->from(Customer::class, 'c')
|
->from(Customer::class, 'c')
|
||||||
->leftJoin('c.teams', 'teams')
|
->leftJoin('c.teams', 'teams')
|
||||||
->andWhere($qb->expr()->in('c.id', $ids))
|
->andWhere($qb->expr()->in('c.id', $customerIds))
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
// do not load team members or leads by default, because they will only be used on detail pages
|
// do not load team members or leads by default, because they will only be used on detail pages
|
||||||
// and there is no benefit in adding multiple queries for most requests when they are only needed in one place
|
if ($hydrateTeamMembers) {
|
||||||
if ($this->fullyHydrated) {
|
|
||||||
$teamIds = [];
|
$teamIds = [];
|
||||||
foreach ($customers as $customer) {
|
foreach ($results as $customer) {
|
||||||
foreach ($customer->getTeams() as $team) {
|
foreach ($customer->getTeams() as $team) {
|
||||||
$teamIds[] = $team->getId();
|
$teamIds[] = $team->getId();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,10 @@
|
|||||||
|
|
||||||
namespace App\Repository\Loader;
|
namespace App\Repository\Loader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use QueryBuilderPaginator instead
|
||||||
|
* @implements LoaderInterface<mixed>
|
||||||
|
*/
|
||||||
final class DefaultLoader implements LoaderInterface
|
final class DefaultLoader implements LoaderInterface
|
||||||
{
|
{
|
||||||
public function loadResults(array $results): void
|
public function loadResults(array $results): void
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Kimai time-tracking app.
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Repository\Loader;
|
|
||||||
|
|
||||||
use App\Entity\Invoice;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
final class InvoiceLoader implements LoaderInterface
|
|
||||||
{
|
|
||||||
public function __construct(private EntityManagerInterface $entityManager)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<int|Invoice> $results
|
|
||||||
*/
|
|
||||||
public function loadResults(array $results): void
|
|
||||||
{
|
|
||||||
if (empty($results)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$ids = array_map(function ($invoice) {
|
|
||||||
if ($invoice instanceof Invoice) {
|
|
||||||
// make sure that this potential doctrine proxy is initialized and filled with all data
|
|
||||||
$invoice->getInvoiceNumber();
|
|
||||||
|
|
||||||
return $invoice->getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $invoice;
|
|
||||||
}, $results);
|
|
||||||
|
|
||||||
$em = $this->entityManager;
|
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
|
||||||
$qb->select('PARTIAL i.{id}', 'customer')
|
|
||||||
->from(Invoice::class, 'i')
|
|
||||||
->leftJoin('i.customer', 'customer')
|
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
|
||||||
$qb->select('PARTIAL i.{id}', 'user')
|
|
||||||
->from(Invoice::class, 'i')
|
|
||||||
->leftJoin('i.user', 'user')
|
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
|
||||||
$qb->select('PARTIAL i.{id}', 'meta')
|
|
||||||
->from(Invoice::class, 'i')
|
|
||||||
->leftJoin('i.meta', 'meta')
|
|
||||||
->andWhere($qb->expr()->in('i.id', $ids))
|
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,12 +9,15 @@
|
|||||||
|
|
||||||
namespace App\Repository\Loader;
|
namespace App\Repository\Loader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
*/
|
||||||
interface LoaderInterface
|
interface LoaderInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Prepares the given database results, to prevent lazy loading.
|
* Prepares the given database results, to prevent lazy loading.
|
||||||
*
|
*
|
||||||
* @param array $results
|
* @param array<array-key, T> $results
|
||||||
*/
|
*/
|
||||||
public function loadResults(array $results): void;
|
public function loadResults(array $results): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,63 +14,50 @@ use App\Entity\Project;
|
|||||||
use App\Entity\Team;
|
use App\Entity\Team;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @implements LoaderInterface<Project>
|
||||||
|
*/
|
||||||
final class ProjectLoader implements LoaderInterface
|
final class ProjectLoader implements LoaderInterface
|
||||||
{
|
{
|
||||||
public function __construct(private EntityManagerInterface $entityManager, private bool $hydrateTeamMembers = false, private bool $hydrateTeams = true, private bool $hydrateMeta = true)
|
public function __construct(
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly bool $hydrateTeamMembers = false,
|
||||||
|
private readonly bool $hydrateTeams = true
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int|Project> $results
|
* @param array<Project> $results
|
||||||
*/
|
*/
|
||||||
public function loadResults(array $results): void
|
public function loadResults(array $results): void
|
||||||
{
|
{
|
||||||
if (empty($results)) {
|
if (\count($results) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$ids = array_map(function ($project) {
|
$projectIds = array_filter(array_unique(array_map(function (Project $project) {
|
||||||
if ($project instanceof Project) {
|
|
||||||
// make sure that this potential doctrine proxy is initialized and filled with all data
|
// make sure that this potential doctrine proxy is initialized and filled with all data
|
||||||
$project->getName();
|
$project->getName();
|
||||||
|
// using reporting controller tests will show that error
|
||||||
|
$project->getCustomer()?->getName();
|
||||||
|
|
||||||
return $project->getId();
|
return $project->getId();
|
||||||
}
|
}, $results)), function ($value) { return $value !== null; });
|
||||||
|
|
||||||
return $project;
|
|
||||||
}, $results);
|
|
||||||
|
|
||||||
$em = $this->entityManager;
|
$em = $this->entityManager;
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
|
||||||
/** @var Project[] $projects */
|
|
||||||
$projects = $qb->select('PARTIAL project.{id}', 'customer')
|
|
||||||
->from(Project::class, 'project')
|
|
||||||
->leftJoin('project.customer', 'customer')
|
|
||||||
->andWhere($qb->expr()->in('project.id', $ids))
|
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$customerIds = array_unique(array_map(function (Project $project) {
|
|
||||||
return $project->getCustomer()->getId();
|
|
||||||
}, $projects));
|
|
||||||
|
|
||||||
if ($this->hydrateMeta) {
|
|
||||||
$qb = $em->createQueryBuilder();
|
|
||||||
$qb->select('PARTIAL project.{id}', 'meta')
|
|
||||||
->from(Project::class, 'project')
|
|
||||||
->leftJoin('project.meta', 'meta')
|
|
||||||
->andWhere($qb->expr()->in('project.id', $ids))
|
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->hydrateTeams) {
|
if ($this->hydrateTeams) {
|
||||||
|
$customerIds = array_filter(array_unique(array_map(function (Project $project) {
|
||||||
|
return $project->getCustomer()->getId();
|
||||||
|
}, $results)), function ($value) { return $value !== null; });
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
$qb = $em->createQueryBuilder();
|
||||||
$qb->select('PARTIAL project.{id}', 'teams')
|
$qb->select('PARTIAL project.{id}', 'teams')
|
||||||
->from(Project::class, 'project')
|
->from(Project::class, 'project')
|
||||||
->leftJoin('project.teams', 'teams')
|
->leftJoin('project.teams', 'teams')
|
||||||
->andWhere($qb->expr()->in('project.id', $ids))
|
->andWhere($qb->expr()->in('project.id', $projectIds))
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
@@ -87,7 +74,7 @@ final class ProjectLoader implements LoaderInterface
|
|||||||
// and there is no benefit in adding multiple queries for most requests when they are only needed in one place
|
// and there is no benefit in adding multiple queries for most requests when they are only needed in one place
|
||||||
if ($this->hydrateTeamMembers) {
|
if ($this->hydrateTeamMembers) {
|
||||||
$teamIds = [];
|
$teamIds = [];
|
||||||
foreach ($projects as $project) {
|
foreach ($results as $project) {
|
||||||
foreach ($project->getTeams() as $team) {
|
foreach ($project->getTeams() as $team) {
|
||||||
$teamIds[] = $team->getId();
|
$teamIds[] = $team->getId();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,52 +9,77 @@
|
|||||||
|
|
||||||
namespace App\Repository\Loader;
|
namespace App\Repository\Loader;
|
||||||
|
|
||||||
|
use App\Entity\Project;
|
||||||
use App\Entity\Team;
|
use App\Entity\Team;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @implements LoaderInterface<Team>
|
||||||
|
*/
|
||||||
final class TeamLoader implements LoaderInterface
|
final class TeamLoader implements LoaderInterface
|
||||||
{
|
{
|
||||||
public function __construct(private EntityManagerInterface $entityManager)
|
public function __construct(
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly bool $loadCustomer = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int|Team> $results
|
* @param array<Team> $results
|
||||||
*/
|
*/
|
||||||
public function loadResults(array $results): void
|
public function loadResults(array $results): void
|
||||||
{
|
{
|
||||||
if (empty($results)) {
|
if (\count($results) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$ids = array_map(function ($team) {
|
$teamIds = array_filter(array_unique(array_map(function (Team $team) {
|
||||||
if ($team instanceof Team) {
|
|
||||||
// make sure that this potential doctrine proxy is initialized and filled with all data
|
// make sure that this potential doctrine proxy is initialized and filled with all data
|
||||||
$team->getName();
|
$team->getName();
|
||||||
|
|
||||||
return $team->getId();
|
return $team->getId();
|
||||||
}
|
}, $results)), function ($value) { return $value !== null; });
|
||||||
|
|
||||||
return $team;
|
|
||||||
}, $results);
|
|
||||||
|
|
||||||
$em = $this->entityManager;
|
$em = $this->entityManager;
|
||||||
|
|
||||||
|
// required wherever users are shown, e.g. on "Custom details" page
|
||||||
$qb = $em->createQueryBuilder();
|
$qb = $em->createQueryBuilder();
|
||||||
$qb->select('PARTIAL team.{id}', 'members', 'user')
|
$qb->select('PARTIAL team.{id}', 'members', 'user')
|
||||||
->from(Team::class, 'team')
|
->from(Team::class, 'team')
|
||||||
->leftJoin('team.members', 'members')
|
->leftJoin('team.members', 'members')
|
||||||
->leftJoin('members.user', 'user')
|
->leftJoin('members.user', 'user')
|
||||||
->andWhere($qb->expr()->in('team.id', $ids))
|
->andWhere($qb->expr()->in('team.id', $teamIds))
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
|
// used in UserTeamProjects widget
|
||||||
$qb = $em->createQueryBuilder();
|
$qb = $em->createQueryBuilder();
|
||||||
$qb->select('PARTIAL team.{id}', 'projects')
|
/** @var array<Team> $teams */
|
||||||
|
$teams = $qb->select('PARTIAL team.{id}', 'projects')
|
||||||
->from(Team::class, 'team')
|
->from(Team::class, 'team')
|
||||||
->leftJoin('team.projects', 'projects')
|
->leftJoin('team.projects', 'projects')
|
||||||
->andWhere($qb->expr()->in('team.id', $ids))
|
->andWhere($qb->expr()->in('team.id', $teamIds))
|
||||||
|
->getQuery()
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$projectIds = [];
|
||||||
|
foreach ($results as $team) {
|
||||||
|
foreach ($team->getProjects() as $project) {
|
||||||
|
$projectIds[] = $project->getId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->loadCustomer) {
|
||||||
|
// used in UserTeamProjects widget
|
||||||
|
$qb = $em->createQueryBuilder();
|
||||||
|
$qb->select('PARTIAL project.{id}', 'customer')
|
||||||
|
->from(Project::class, 'project')
|
||||||
|
->leftJoin('project.customer', 'customer')
|
||||||
|
->andWhere($qb->expr()->in('project.id', $projectIds))
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->execute();
|
->execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,46 +15,40 @@ use App\Entity\Project;
|
|||||||
use App\Entity\Timesheet;
|
use App\Entity\Timesheet;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @implements LoaderInterface<Timesheet>
|
||||||
|
*/
|
||||||
final class TimesheetLoader implements LoaderInterface
|
final class TimesheetLoader implements LoaderInterface
|
||||||
{
|
{
|
||||||
public function __construct(private EntityManagerInterface $entityManager, private bool $fullyHydrated = false, private bool $basicHydrated = true)
|
public function __construct(
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly bool $fullyHydrated = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int|Timesheet> $results
|
* @param array<Timesheet> $results
|
||||||
*/
|
*/
|
||||||
public function loadResults(array $results): void
|
public function loadResults(array $results): void
|
||||||
{
|
{
|
||||||
if (empty($results)) {
|
if (\count($results) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$ids = array_map(function ($timesheet) {
|
$ids = array_filter(array_unique(array_map(function (Timesheet $timesheet) {
|
||||||
if ($timesheet instanceof Timesheet) {
|
|
||||||
// make sure that this potential doctrine proxy is initialized and filled with all data
|
// make sure that this potential doctrine proxy is initialized and filled with all data
|
||||||
$timesheet->getType();
|
$timesheet->getType();
|
||||||
|
|
||||||
return $timesheet->getId();
|
return $timesheet->getId();
|
||||||
}
|
}, $results)), function ($value) { return $value !== null; });
|
||||||
|
|
||||||
return $timesheet;
|
|
||||||
}, $results);
|
|
||||||
|
|
||||||
$em = $this->entityManager;
|
$em = $this->entityManager;
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
$projectIds = array_filter(array_unique(array_map(function (Timesheet $timesheet) {
|
||||||
/** @var array<Timesheet> $timesheets */
|
return $timesheet->getProject()?->getId();
|
||||||
$timesheets = $qb->select('PARTIAL t.{id}', 'project')
|
}, $results)), function ($value) { return $value !== null; });
|
||||||
->from(Timesheet::class, 't')
|
|
||||||
->leftJoin('t.project', 'project')
|
|
||||||
->andWhere($qb->expr()->in('t.id', $ids))
|
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$projectIds = array_map(function ($timesheet) {
|
|
||||||
return $timesheet->getProject()->getId();
|
|
||||||
}, $timesheets);
|
|
||||||
|
|
||||||
if ($this->fullyHydrated) {
|
if ($this->fullyHydrated) {
|
||||||
$qb = $em->createQueryBuilder();
|
$qb = $em->createQueryBuilder();
|
||||||
@@ -76,9 +70,9 @@ final class TimesheetLoader implements LoaderInterface
|
|||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
if ($this->fullyHydrated) {
|
if ($this->fullyHydrated) {
|
||||||
$customerIds = array_map(function ($project) {
|
$customerIds = array_filter(array_unique(array_map(function (Project $project) {
|
||||||
return $project->getCustomer()->getId();
|
return $project->getCustomer()->getId();
|
||||||
}, $projects);
|
}, $projects)), function ($value) { return $value !== null; });
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
$qb = $em->createQueryBuilder();
|
||||||
$qb->select('PARTIAL c.{id}', 'meta')
|
$qb->select('PARTIAL c.{id}', 'meta')
|
||||||
@@ -89,18 +83,10 @@ final class TimesheetLoader implements LoaderInterface
|
|||||||
->execute();
|
->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
|
||||||
$qb->select('PARTIAL t.{id}', 'activity')
|
|
||||||
->from(Timesheet::class, 't')
|
|
||||||
->leftJoin('t.activity', 'activity')
|
|
||||||
->andWhere($qb->expr()->in('t.id', $ids))
|
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
if ($this->fullyHydrated) {
|
if ($this->fullyHydrated) {
|
||||||
$activityIds = array_filter(array_map(function (Timesheet $timesheet) {
|
$activityIds = array_filter(array_map(function (Timesheet $timesheet) {
|
||||||
return $timesheet->getActivity()?->getId();
|
return $timesheet->getActivity()?->getId();
|
||||||
}, $timesheets), function ($id): bool {
|
}, $results), function ($id): bool {
|
||||||
return $id !== null;
|
return $id !== null;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -113,15 +99,6 @@ final class TimesheetLoader implements LoaderInterface
|
|||||||
->execute();
|
->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->basicHydrated) {
|
|
||||||
$qb = $em->createQueryBuilder();
|
|
||||||
$qb->select('PARTIAL t.{id}', 'user')
|
|
||||||
->from(Timesheet::class, 't')
|
|
||||||
->leftJoin('t.user', 'user')
|
|
||||||
->andWhere($qb->expr()->in('t.id', $ids))
|
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
$qb = $em->createQueryBuilder();
|
||||||
$qb->select('PARTIAL t.{id}', 'tags')
|
$qb->select('PARTIAL t.{id}', 'tags')
|
||||||
->from(Timesheet::class, 't')
|
->from(Timesheet::class, 't')
|
||||||
@@ -129,14 +106,5 @@ final class TimesheetLoader implements LoaderInterface
|
|||||||
->andWhere($qb->expr()->in('t.id', $ids))
|
->andWhere($qb->expr()->in('t.id', $ids))
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
|
||||||
$qb->select('PARTIAL t.{id}', 'meta')
|
|
||||||
->from(Timesheet::class, 't')
|
|
||||||
->leftJoin('t.meta', 'meta')
|
|
||||||
->andWhere($qb->expr()->in('t.id', $ids))
|
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,49 +13,54 @@ use App\Entity\Team;
|
|||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @implements LoaderInterface<User>
|
||||||
|
*/
|
||||||
final class UserLoader implements LoaderInterface
|
final class UserLoader implements LoaderInterface
|
||||||
{
|
{
|
||||||
public function __construct(private EntityManagerInterface $entityManager, private bool $fullyHydrated = false)
|
public function __construct(
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly bool $fullyHydrated = false
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int|User> $results
|
* @param array<User> $results
|
||||||
*/
|
*/
|
||||||
public function loadResults(array $results): void
|
public function loadResults(array $results): void
|
||||||
{
|
{
|
||||||
if (empty($results)) {
|
if (\count($results) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$ids = array_map(function ($user) {
|
$userIds = array_filter(array_unique(array_map(function (User $user) {
|
||||||
if ($user instanceof User) {
|
|
||||||
// make sure that this potential doctrine proxy is initialized and filled with all data
|
// make sure that this potential doctrine proxy is initialized and filled with all data
|
||||||
$user->getDisplayName();
|
$user->getDisplayName();
|
||||||
|
|
||||||
return $user->getId();
|
return $user->getId();
|
||||||
}
|
}, $results)), function ($value) { return $value !== null; });
|
||||||
|
|
||||||
return $user;
|
|
||||||
}, $results);
|
|
||||||
|
|
||||||
$em = $this->entityManager;
|
$em = $this->entityManager;
|
||||||
|
|
||||||
|
// this is currently needed, as it does not work via the Doctrine eager fetch method
|
||||||
|
// on user listing pages, if users are already in the unit of work from another load
|
||||||
|
$qb = $em->createQueryBuilder();
|
||||||
|
$qb->select('PARTIAL user.{id}', 'preferences')
|
||||||
|
->from(User::class, 'user')
|
||||||
|
->leftJoin('user.preferences', 'preferences')
|
||||||
|
->andWhere($qb->expr()->in('user.id', $userIds))
|
||||||
|
->getQuery()
|
||||||
|
->execute();
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
$qb = $em->createQueryBuilder();
|
||||||
/** @var User[] $users */
|
/** @var User[] $users */
|
||||||
$users = $qb->select('PARTIAL user.{id}', 'memberships', 'team')
|
$users = $qb->select('PARTIAL user.{id}', 'memberships', 'team')
|
||||||
->from(User::class, 'user')
|
->from(User::class, 'user')
|
||||||
->leftJoin('user.memberships', 'memberships')
|
->leftJoin('user.memberships', 'memberships')
|
||||||
->leftJoin('memberships.team', 'team')
|
->leftJoin('memberships.team', 'team')
|
||||||
->andWhere($qb->expr()->in('user.id', $ids))
|
->andWhere($qb->expr()->in('user.id', $userIds))
|
||||||
->getQuery()
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$qb = $em->createQueryBuilder();
|
|
||||||
$qb->select('PARTIAL user.{id}', 'preferences')
|
|
||||||
->from(User::class, 'user')
|
|
||||||
->leftJoin('user.preferences', 'preferences')
|
|
||||||
->andWhere($qb->expr()->in('user.id', $ids))
|
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,21 @@ use App\Repository\Loader\LoaderInterface;
|
|||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @implements PaginatorInterface<T>
|
||||||
|
*/
|
||||||
final class LoaderPaginator implements PaginatorInterface
|
final class LoaderPaginator implements PaginatorInterface
|
||||||
{
|
{
|
||||||
public function __construct(private LoaderInterface $loader, private QueryBuilder $query, private int $results)
|
/**
|
||||||
|
* @param LoaderInterface<T> $loader
|
||||||
|
* @param int<0, max> $results
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private readonly LoaderInterface $loader,
|
||||||
|
private readonly QueryBuilder $queryBuilder,
|
||||||
|
private readonly int $results
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,12 +37,20 @@ final class LoaderPaginator implements PaginatorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return iterable<array-key, iterable<mixed>>
|
* @return Query<null, T>
|
||||||
|
*/
|
||||||
|
private function getQuery(): Query
|
||||||
|
{
|
||||||
|
return $this->queryBuilder->getQuery(); // @phpstan-ignore-line
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iterable<array-key, T>
|
||||||
*/
|
*/
|
||||||
public function getSlice(int $offset, int $length): iterable
|
public function getSlice(int $offset, int $length): iterable
|
||||||
{
|
{
|
||||||
$query = $this->query
|
/** @var Query<null, T> $query */
|
||||||
->getQuery()
|
$query = $this->getQuery()
|
||||||
->setFirstResult($offset)
|
->setFirstResult($offset)
|
||||||
->setMaxResults($length);
|
->setMaxResults($length);
|
||||||
|
|
||||||
@@ -38,20 +58,24 @@ final class LoaderPaginator implements PaginatorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Query<null, mixed> $query
|
* @param Query<null, T> $query
|
||||||
* @return iterable<array-key, iterable<mixed>>
|
* @return array<array-key, T>
|
||||||
*/
|
*/
|
||||||
private function getResults(Query $query)
|
private function getResults(Query $query): array
|
||||||
{
|
{
|
||||||
|
/** @var array<array-key, T> $results */
|
||||||
$results = $query->execute();
|
$results = $query->execute();
|
||||||
|
|
||||||
$this->loader->loadResults($results);
|
$this->loader->loadResults($results);
|
||||||
|
|
||||||
return $results; // @phpstan-ignore-line
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iterable<array-key, T>
|
||||||
|
*/
|
||||||
public function getAll(): iterable
|
public function getAll(): iterable
|
||||||
{
|
{
|
||||||
return $this->getResults($this->query->getQuery());
|
return $this->getResults($this->getQuery());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
73
src/Repository/Paginator/LoaderQueryPaginator.php
Normal file
73
src/Repository/Paginator/LoaderQueryPaginator.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Repository\Paginator;
|
||||||
|
|
||||||
|
use App\Repository\Loader\LoaderInterface;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @implements PaginatorInterface<T>
|
||||||
|
*/
|
||||||
|
final class LoaderQueryPaginator implements PaginatorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param LoaderInterface<T> $loader
|
||||||
|
* @param Query<T> $query
|
||||||
|
* @param int<0, max> $results
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private readonly LoaderInterface $loader,
|
||||||
|
private readonly Query $query,
|
||||||
|
private readonly int $results
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNbResults(): int
|
||||||
|
{
|
||||||
|
return $this->results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iterable<array-key, T>
|
||||||
|
*/
|
||||||
|
public function getSlice(int $offset, int $length): iterable
|
||||||
|
{
|
||||||
|
/** @var Query<null, T> $query */
|
||||||
|
$query = $this->query
|
||||||
|
->setFirstResult($offset)
|
||||||
|
->setMaxResults($length);
|
||||||
|
|
||||||
|
return $this->getResults($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Query<null, T> $query
|
||||||
|
* @return iterable<array-key, T>
|
||||||
|
*/
|
||||||
|
private function getResults(Query $query): iterable
|
||||||
|
{
|
||||||
|
/** @var array<T> $results */
|
||||||
|
$results = $query->execute();
|
||||||
|
|
||||||
|
$this->loader->loadResults($results);
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iterable<array-key, T>
|
||||||
|
*/
|
||||||
|
public function getAll(): iterable
|
||||||
|
{
|
||||||
|
return $this->getResults($this->query);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,12 +11,15 @@ namespace App\Repository\Paginator;
|
|||||||
|
|
||||||
use Pagerfanta\Adapter\AdapterInterface;
|
use Pagerfanta\Adapter\AdapterInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template-covariant T
|
||||||
|
*/
|
||||||
interface PaginatorInterface extends AdapterInterface
|
interface PaginatorInterface extends AdapterInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Returns all available results without pagination.
|
* Returns all available results without pagination.
|
||||||
*
|
*
|
||||||
* @return iterable
|
* @return iterable<array-key, T>
|
||||||
*/
|
*/
|
||||||
public function getAll(): iterable;
|
public function getAll(): iterable;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,19 @@ namespace App\Repository\Paginator;
|
|||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use QueryPaginator instead
|
||||||
|
* @implements PaginatorInterface<mixed>
|
||||||
|
*/
|
||||||
final class QueryBuilderPaginator implements PaginatorInterface
|
final class QueryBuilderPaginator implements PaginatorInterface
|
||||||
{
|
{
|
||||||
public function __construct(private QueryBuilder $query, private int $results)
|
/**
|
||||||
|
* @param int<0, max> $results
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private readonly QueryBuilder $queryBuilder,
|
||||||
|
private readonly int $results
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,11 +34,12 @@ final class QueryBuilderPaginator implements PaginatorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return iterable<array-key, iterable<mixed>>
|
* @return iterable<array-key, mixed>
|
||||||
*/
|
*/
|
||||||
public function getSlice(int $offset, int $length): iterable
|
public function getSlice(int $offset, int $length): iterable
|
||||||
{
|
{
|
||||||
$query = $this->query
|
/** @var Query<null, mixed> $query */
|
||||||
|
$query = $this->queryBuilder
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->setFirstResult($offset)
|
->setFirstResult($offset)
|
||||||
->setMaxResults($length);
|
->setMaxResults($length);
|
||||||
@@ -38,15 +49,18 @@ final class QueryBuilderPaginator implements PaginatorInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Query<null, mixed> $query
|
* @param Query<null, mixed> $query
|
||||||
* @return iterable<array-key, iterable<mixed>>
|
* @return iterable<array-key, mixed>
|
||||||
*/
|
*/
|
||||||
private function getResults(Query $query)
|
private function getResults(Query $query): iterable
|
||||||
{
|
{
|
||||||
return $query->execute(); // @phpstan-ignore-line
|
return $query->execute(); // @phpstan-ignore-line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iterable<array-key, mixed>
|
||||||
|
*/
|
||||||
public function getAll(): iterable
|
public function getAll(): iterable
|
||||||
{
|
{
|
||||||
return $this->getResults($this->query->getQuery());
|
return $this->getResults($this->queryBuilder->getQuery());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
64
src/Repository/Paginator/QueryPaginator.php
Normal file
64
src/Repository/Paginator/QueryPaginator.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Repository\Paginator;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @implements PaginatorInterface<T>
|
||||||
|
*/
|
||||||
|
final class QueryPaginator implements PaginatorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Query<null, T> $query
|
||||||
|
* @param int<0, max> $results
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
private readonly Query $query,
|
||||||
|
private readonly int $results
|
||||||
|
)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNbResults(): int
|
||||||
|
{
|
||||||
|
return $this->results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iterable<array-key, T>
|
||||||
|
*/
|
||||||
|
public function getSlice(int $offset, int $length): iterable
|
||||||
|
{
|
||||||
|
$query = $this->query
|
||||||
|
->setFirstResult($offset)
|
||||||
|
->setMaxResults($length);
|
||||||
|
|
||||||
|
return $this->getResults($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Query<null, T> $query
|
||||||
|
* @return iterable<array-key, T>
|
||||||
|
*/
|
||||||
|
private function getResults(Query $query): iterable
|
||||||
|
{
|
||||||
|
return $query->execute(); // @phpstan-ignore-line
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iterable<array-key, T>
|
||||||
|
*/
|
||||||
|
public function getAll(): iterable
|
||||||
|
{
|
||||||
|
return $this->getResults($this->query);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,16 +17,18 @@ use App\Entity\Team;
|
|||||||
use App\Entity\Timesheet;
|
use App\Entity\Timesheet;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Repository\Loader\ProjectLoader;
|
use App\Repository\Loader\ProjectLoader;
|
||||||
use App\Repository\Paginator\LoaderPaginator;
|
use App\Repository\Paginator\LoaderQueryPaginator;
|
||||||
use App\Repository\Paginator\PaginatorInterface;
|
use App\Repository\Paginator\PaginatorInterface;
|
||||||
use App\Repository\Query\ProjectFormTypeQuery;
|
use App\Repository\Query\ProjectFormTypeQuery;
|
||||||
use App\Repository\Query\ProjectQuery;
|
use App\Repository\Query\ProjectQuery;
|
||||||
|
use App\Repository\Query\ProjectQueryHydrate;
|
||||||
use App\Utils\Pagination;
|
use App\Utils\Pagination;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Doctrine\DBAL\ParameterType;
|
use Doctrine\DBAL\ParameterType;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\ORM\Exception\ORMException;
|
use Doctrine\ORM\Exception\ORMException;
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
use Doctrine\ORM\Query;
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\Query\Expr\Andx;
|
use Doctrine\ORM\Query\Expr\Andx;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
@@ -40,30 +42,31 @@ class ProjectRepository extends EntityRepository
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int[] $projectIds
|
* @param int[] $projectIds
|
||||||
* @return Project[]
|
* @return array<Project>
|
||||||
*/
|
*/
|
||||||
public function findByIds(array $projectIds): array
|
public function findByIds(array $projectIds): array
|
||||||
{
|
{
|
||||||
|
$ids = array_filter(
|
||||||
|
array_unique($projectIds),
|
||||||
|
function ($value) {
|
||||||
|
return $value > 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (\count($ids) === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
$qb = $this->createQueryBuilder('p');
|
$qb = $this->createQueryBuilder('p');
|
||||||
$qb
|
$qb
|
||||||
->where($qb->expr()->in('p.id', ':id'))
|
->where($qb->expr()->in('p.id', ':id'))
|
||||||
->setParameter('id', $projectIds)
|
->setParameter('id', $ids)
|
||||||
;
|
;
|
||||||
|
|
||||||
$projects = $qb->getQuery()->getResult();
|
return $this->getProjects($this->prepareProjectQuery($qb->getQuery()));
|
||||||
|
|
||||||
$loader = new ProjectLoader($qb->getEntityManager(), true);
|
|
||||||
$loader->loadResults($projects);
|
|
||||||
|
|
||||||
return $projects;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function saveProject(Project $project): void
|
||||||
* @param Project $project
|
|
||||||
* @throws ORMException
|
|
||||||
* @throws \Doctrine\ORM\OptimisticLockException
|
|
||||||
*/
|
|
||||||
public function saveProject(Project $project)
|
|
||||||
{
|
{
|
||||||
$entityManager = $this->getEntityManager();
|
$entityManager = $this->getEntityManager();
|
||||||
$entityManager->persist($project);
|
$entityManager->persist($project);
|
||||||
@@ -71,13 +74,12 @@ class ProjectRepository extends EntityRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param null|bool $visible
|
* @return int<0, max>
|
||||||
* @return int
|
|
||||||
*/
|
*/
|
||||||
public function countProject($visible = null): int
|
public function countProject(?bool $visible = null): int
|
||||||
{
|
{
|
||||||
if (null !== $visible) {
|
if (null !== $visible) {
|
||||||
return $this->count(['visible' => (bool) $visible]);
|
return $this->count(['visible' => $visible]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->count([]);
|
return $this->count([]);
|
||||||
@@ -91,7 +93,7 @@ class ProjectRepository extends EntityRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = []): Andx
|
private function getPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = []): Andx
|
||||||
{
|
{
|
||||||
$andX = $qb->expr()->andX();
|
$andX = $qb->expr()->andX();
|
||||||
|
|
||||||
@@ -140,8 +142,7 @@ class ProjectRepository extends EntityRepository
|
|||||||
/**
|
/**
|
||||||
* Returns a query builder that is used for ProjectType and your own 'query_builder' option.
|
* Returns a query builder that is used for ProjectType and your own 'query_builder' option.
|
||||||
*
|
*
|
||||||
* @param ProjectFormTypeQuery $query
|
* @internal
|
||||||
* @return QueryBuilder
|
|
||||||
*/
|
*/
|
||||||
public function getQueryBuilderForFormType(ProjectFormTypeQuery $query): QueryBuilder
|
public function getQueryBuilderForFormType(ProjectFormTypeQuery $query): QueryBuilder
|
||||||
{
|
{
|
||||||
@@ -213,6 +214,10 @@ class ProjectRepository extends EntityRepository
|
|||||||
->leftJoin('p.customer', 'c')
|
->leftJoin('p.customer', 'c')
|
||||||
;
|
;
|
||||||
|
|
||||||
|
if (\count($query->getProjectIds()) > 0) {
|
||||||
|
$qb->andWhere($qb->expr()->in('p.id', ':id'))->setParameter('id', $query->getProjectIds());
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($query->getOrderGroups() as $orderBy => $order) {
|
foreach ($query->getOrderGroups() as $orderBy => $order) {
|
||||||
switch ($orderBy) {
|
switch ($orderBy) {
|
||||||
case 'customer':
|
case 'customer':
|
||||||
@@ -332,6 +337,9 @@ class ProjectRepository extends EntityRepository
|
|||||||
return $and;
|
return $and;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int<0, max>
|
||||||
|
*/
|
||||||
public function countProjectsForQuery(ProjectQuery $query): int
|
public function countProjectsForQuery(ProjectQuery $query): int
|
||||||
{
|
{
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$qb = $this->getQueryBuilderForQuery($query);
|
||||||
@@ -342,7 +350,7 @@ class ProjectRepository extends EntityRepository
|
|||||||
->select($qb->expr()->countDistinct('p.id'))
|
->select($qb->expr()->countDistinct('p.id'))
|
||||||
;
|
;
|
||||||
|
|
||||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
return (int) $qb->getQuery()->getSingleScalarResult(); // @phpstan-ignore-line
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPagerfantaForQuery(ProjectQuery $query): Pagination
|
public function getPagerfantaForQuery(ProjectQuery $query): Pagination
|
||||||
@@ -350,34 +358,41 @@ class ProjectRepository extends EntityRepository
|
|||||||
return new Pagination($this->getPaginatorForQuery($query), $query);
|
return new Pagination($this->getPaginatorForQuery($query), $query);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getPaginatorForQuery(ProjectQuery $query): PaginatorInterface
|
/**
|
||||||
|
* @return PaginatorInterface<Project>
|
||||||
|
*/
|
||||||
|
private function getPaginatorForQuery(ProjectQuery $projectQuery): PaginatorInterface
|
||||||
{
|
{
|
||||||
$counter = $this->countProjectsForQuery($query);
|
$counter = $this->countProjectsForQuery($projectQuery);
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$query = $this->createProjectQuery($projectQuery);
|
||||||
|
|
||||||
return new LoaderPaginator(new ProjectLoader($qb->getEntityManager()), $qb, $counter);
|
return new LoaderQueryPaginator(new ProjectLoader($this->getEntityManager(), false, true), $query, $counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ProjectQuery $query
|
|
||||||
* @return Project[]
|
* @return Project[]
|
||||||
*/
|
*/
|
||||||
public function getProjectsForQuery(ProjectQuery $query): iterable
|
public function getProjectsForQuery(ProjectQuery $query): array
|
||||||
{
|
{
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
return $this->getProjects($this->createProjectQuery($query));
|
||||||
$results = $qb->getQuery()->execute();
|
|
||||||
$loader = new ProjectLoader($qb->getEntityManager());
|
|
||||||
$loader->loadResults($results);
|
|
||||||
|
|
||||||
return $results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Project $delete
|
* @param Query<Project> $query
|
||||||
* @param Project|null $replace
|
* @return Project[]
|
||||||
* @throws \Doctrine\ORM\Exception\ORMException
|
|
||||||
*/
|
*/
|
||||||
public function deleteProject(Project $delete, ?Project $replace = null)
|
public function getProjects(Query $query): array
|
||||||
|
{
|
||||||
|
/** @var array<Project> $projects */
|
||||||
|
$projects = $query->execute();
|
||||||
|
|
||||||
|
$loader = new ProjectLoader($this->getEntityManager(), false, true);
|
||||||
|
$loader->loadResults($projects);
|
||||||
|
|
||||||
|
return $projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteProject(Project $delete, ?Project $replace = null): void
|
||||||
{
|
{
|
||||||
$em = $this->getEntityManager();
|
$em = $this->getEntityManager();
|
||||||
$em->beginTransaction();
|
$em->beginTransaction();
|
||||||
@@ -429,17 +444,58 @@ class ProjectRepository extends EntityRepository
|
|||||||
return $qb->getQuery()->getResult();
|
return $qb->getQuery()->getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveComment(ProjectComment $comment)
|
public function saveComment(ProjectComment $comment): void
|
||||||
{
|
{
|
||||||
$entityManager = $this->getEntityManager();
|
$entityManager = $this->getEntityManager();
|
||||||
$entityManager->persist($comment);
|
$entityManager->persist($comment);
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteComment(ProjectComment $comment)
|
public function deleteComment(ProjectComment $comment): void
|
||||||
{
|
{
|
||||||
$entityManager = $this->getEntityManager();
|
$entityManager = $this->getEntityManager();
|
||||||
$entityManager->remove($comment);
|
$entityManager->remove($comment);
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Query<Project>
|
||||||
|
*/
|
||||||
|
private function createProjectQuery(ProjectQuery $projectQuery): Query
|
||||||
|
{
|
||||||
|
$query = $this->getQueryBuilderForQuery($projectQuery)->getQuery();
|
||||||
|
$query = $this->prepareProjectQuery($query);
|
||||||
|
|
||||||
|
foreach ($projectQuery->getHydrate() as $hydrate) {
|
||||||
|
switch ($hydrate) {
|
||||||
|
case ProjectQueryHydrate::TEAMS:
|
||||||
|
// does not yet work, see https://github.com/doctrine/orm/pull/8391
|
||||||
|
// $query->setFetchMode(Project::class, 'teams', ClassMetadata::FETCH_EAGER);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ProjectQueryHydrate::TEAM_MEMBER:
|
||||||
|
// does not yet work, see https://github.com/doctrine/orm/issues/11254
|
||||||
|
// $query->setFetchMode(Project::class, 'teams', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(Team::class, 'members', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(TeamMember::class, 'user', ClassMetadata::FETCH_EAGER);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Query<Project> $query
|
||||||
|
* @return Query<Project>
|
||||||
|
*/
|
||||||
|
public function prepareProjectQuery(Query $query): Query
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->getConfiguration()->setEagerFetchBatchSize(300);
|
||||||
|
|
||||||
|
$query->setFetchMode(Project::class, 'meta', ClassMetadata::FETCH_EAGER);
|
||||||
|
$query->setFetchMode(Project::class, 'customer', ClassMetadata::FETCH_EAGER);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ use App\Entity\Project;
|
|||||||
/**
|
/**
|
||||||
* Can be used for advanced queries with the: ActivityRepository
|
* Can be used for advanced queries with the: ActivityRepository
|
||||||
*/
|
*/
|
||||||
class ActivityQuery extends ProjectQuery
|
class ActivityQuery extends BaseQuery implements VisibilityInterface
|
||||||
{
|
{
|
||||||
|
use VisibilityTrait;
|
||||||
|
use CustomerTrait;
|
||||||
|
|
||||||
public const ACTIVITY_ORDER_ALLOWED = [
|
public const ACTIVITY_ORDER_ALLOWED = [
|
||||||
'name',
|
'name',
|
||||||
'description' => 'comment',
|
'description' => 'comment',
|
||||||
@@ -33,18 +36,45 @@ class ActivityQuery extends ProjectQuery
|
|||||||
private array $projects = [];
|
private array $projects = [];
|
||||||
private bool $globalsOnly = false;
|
private bool $globalsOnly = false;
|
||||||
private bool $excludeGlobals = false;
|
private bool $excludeGlobals = false;
|
||||||
|
/**
|
||||||
|
* @var array<int>
|
||||||
|
*/
|
||||||
|
private array $activityIds = [];
|
||||||
|
/**
|
||||||
|
* @var array<ActivityQueryHydrate>
|
||||||
|
*/
|
||||||
|
private array $hydrate = [];
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct();
|
|
||||||
$this->setDefaults([
|
$this->setDefaults([
|
||||||
'orderBy' => 'name',
|
'orderBy' => 'name',
|
||||||
|
'customers' => [],
|
||||||
'projects' => [],
|
'projects' => [],
|
||||||
'globalsOnly' => false,
|
'globalsOnly' => false,
|
||||||
'excludeGlobals' => false,
|
'excludeGlobals' => false,
|
||||||
|
'activityIds' => [],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function copyFrom(BaseQuery $query): void
|
||||||
|
{
|
||||||
|
parent::copyFrom($query);
|
||||||
|
|
||||||
|
if (method_exists($query, 'getCustomers')) {
|
||||||
|
$this->setCustomers($query->getCustomers());
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($query instanceof ActivityQuery) {
|
||||||
|
$this->setActivityIds($query->getActivityIds());
|
||||||
|
$this->setGlobalsOnly($query->isGlobalsOnly());
|
||||||
|
$this->setExcludeGlobals($query->isExcludeGlobals());
|
||||||
|
foreach ($query->getHydrate() as $hydrate) {
|
||||||
|
$this->addHydrate($hydrate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function isGlobalsOnly(): bool
|
public function isGlobalsOnly(): bool
|
||||||
{
|
{
|
||||||
return $this->globalsOnly;
|
return $this->globalsOnly;
|
||||||
@@ -115,4 +145,40 @@ class ActivityQuery extends ProjectQuery
|
|||||||
{
|
{
|
||||||
return !empty($this->projects);
|
return !empty($this->projects);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int> $ids
|
||||||
|
*/
|
||||||
|
public function setActivityIds(array $ids): void
|
||||||
|
{
|
||||||
|
$this->activityIds = $ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
public function getActivityIds(): array
|
||||||
|
{
|
||||||
|
return $this->activityIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addHydrate(ActivityQueryHydrate $hydrate): void
|
||||||
|
{
|
||||||
|
if (!\in_array($hydrate, $this->hydrate, true)) {
|
||||||
|
$this->hydrate[] = $hydrate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ActivityQueryHydrate[]
|
||||||
|
*/
|
||||||
|
public function getHydrate(): array
|
||||||
|
{
|
||||||
|
return $this->hydrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadTeams(): void
|
||||||
|
{
|
||||||
|
$this->addHydrate(ActivityQueryHydrate::TEAMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/Repository/Query/ActivityQueryHydrate.php
Normal file
16
src/Repository/Query/ActivityQueryHydrate.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Repository\Query;
|
||||||
|
|
||||||
|
enum ActivityQueryHydrate
|
||||||
|
{
|
||||||
|
case TEAMS;
|
||||||
|
case TEAM_MEMBER;
|
||||||
|
}
|
||||||
@@ -316,7 +316,13 @@ class BaseQuery
|
|||||||
return array_pop($shortClass);
|
return array_pop($shortClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function copyTo(BaseQuery $query): BaseQuery
|
/**
|
||||||
|
* @template T of BaseQuery
|
||||||
|
* @param T $query
|
||||||
|
* @return T
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final public function copyTo(BaseQuery $query): BaseQuery
|
||||||
{
|
{
|
||||||
$query->setDefaults($this->defaults);
|
$query->setDefaults($this->defaults);
|
||||||
if (null !== $this->getCurrentUser()) {
|
if (null !== $this->getCurrentUser()) {
|
||||||
@@ -336,9 +342,15 @@ class BaseQuery
|
|||||||
$query->setVisibility($this->getVisibility());
|
$query->setVisibility($this->getVisibility());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$query->copyFrom($this);
|
||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function copyFrom(BaseQuery $query): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public function isDefaultFilter(string $filter): bool
|
public function isDefaultFilter(string $filter): bool
|
||||||
{
|
{
|
||||||
if (!\array_key_exists($filter, $this->defaults)) {
|
if (!\array_key_exists($filter, $this->defaults)) {
|
||||||
|
|||||||
@@ -14,11 +14,33 @@ class CustomerQuery extends BaseQuery implements VisibilityInterface
|
|||||||
use VisibilityTrait;
|
use VisibilityTrait;
|
||||||
|
|
||||||
public const CUSTOMER_ORDER_ALLOWED = [
|
public const CUSTOMER_ORDER_ALLOWED = [
|
||||||
'name', 'description' => 'comment', 'country', 'number', 'homepage', 'email', 'mobile', 'fax',
|
'name',
|
||||||
'phone', 'currency', 'address', 'contact', 'company', 'vat_id', 'budget', 'timeBudget', 'visible'
|
'description' => 'comment',
|
||||||
|
'country', 'number',
|
||||||
|
'homepage',
|
||||||
|
'email',
|
||||||
|
'mobile',
|
||||||
|
'fax',
|
||||||
|
'phone',
|
||||||
|
'currency',
|
||||||
|
'address',
|
||||||
|
'contact',
|
||||||
|
'company',
|
||||||
|
'vat_id',
|
||||||
|
'budget',
|
||||||
|
'timeBudget',
|
||||||
|
'visible'
|
||||||
];
|
];
|
||||||
|
|
||||||
private ?string $country = null;
|
private ?string $country = null;
|
||||||
|
/**
|
||||||
|
* @var array<int>
|
||||||
|
*/
|
||||||
|
private array $customerIds = [];
|
||||||
|
/**
|
||||||
|
* @var array<CustomerQueryHydrate>
|
||||||
|
*/
|
||||||
|
private array $hydrate = [];
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -26,9 +48,23 @@ class CustomerQuery extends BaseQuery implements VisibilityInterface
|
|||||||
'orderBy' => 'name',
|
'orderBy' => 'name',
|
||||||
'visibility' => VisibilityInterface::SHOW_VISIBLE,
|
'visibility' => VisibilityInterface::SHOW_VISIBLE,
|
||||||
'country' => null,
|
'country' => null,
|
||||||
|
'customerIds' => [],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function copyFrom(BaseQuery $query): void
|
||||||
|
{
|
||||||
|
parent::copyFrom($query);
|
||||||
|
|
||||||
|
if ($query instanceof CustomerQuery) {
|
||||||
|
$this->setCustomerIds($query->getCustomerIds());
|
||||||
|
$this->setCountry($query->getCountry());
|
||||||
|
foreach ($query->getHydrate() as $hydrate) {
|
||||||
|
$this->addHydrate($hydrate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getCountry(): ?string
|
public function getCountry(): ?string
|
||||||
{
|
{
|
||||||
return $this->country;
|
return $this->country;
|
||||||
@@ -38,4 +74,40 @@ class CustomerQuery extends BaseQuery implements VisibilityInterface
|
|||||||
{
|
{
|
||||||
$this->country = $country;
|
$this->country = $country;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int> $ids
|
||||||
|
*/
|
||||||
|
public function setCustomerIds(array $ids): void
|
||||||
|
{
|
||||||
|
$this->customerIds = $ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
public function getCustomerIds(): array
|
||||||
|
{
|
||||||
|
return $this->customerIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addHydrate(CustomerQueryHydrate $hydrate): void
|
||||||
|
{
|
||||||
|
if (!\in_array($hydrate, $this->hydrate, true)) {
|
||||||
|
$this->hydrate[] = $hydrate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return CustomerQueryHydrate[]
|
||||||
|
*/
|
||||||
|
public function getHydrate(): array
|
||||||
|
{
|
||||||
|
return $this->hydrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadTeams(): void
|
||||||
|
{
|
||||||
|
$this->addHydrate(CustomerQueryHydrate::TEAMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/Repository/Query/CustomerQueryHydrate.php
Normal file
16
src/Repository/Query/CustomerQueryHydrate.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Repository\Query;
|
||||||
|
|
||||||
|
enum CustomerQueryHydrate
|
||||||
|
{
|
||||||
|
case TEAMS;
|
||||||
|
case TEAM_MEMBER;
|
||||||
|
}
|
||||||
63
src/Repository/Query/CustomerTrait.php
Normal file
63
src/Repository/Query/CustomerTrait.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Repository\Query;
|
||||||
|
|
||||||
|
use App\Entity\Customer;
|
||||||
|
|
||||||
|
trait CustomerTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array<Customer>
|
||||||
|
*/
|
||||||
|
private array $customers = [];
|
||||||
|
|
||||||
|
public function addCustomer(Customer $customer): self
|
||||||
|
{
|
||||||
|
$this->customers[] = $customer;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<Customer> $customers
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setCustomers(array $customers): self
|
||||||
|
{
|
||||||
|
$this->customers = $customers;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<Customer>
|
||||||
|
*/
|
||||||
|
public function getCustomers(): array
|
||||||
|
{
|
||||||
|
return $this->customers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int>
|
||||||
|
*/
|
||||||
|
public function getCustomerIds(): array
|
||||||
|
{
|
||||||
|
return array_filter(array_values(array_unique(array_map(function (Customer $customer) {
|
||||||
|
return $customer->getId();
|
||||||
|
}, $this->customers))), function ($id) {
|
||||||
|
return $id !== null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasCustomers(): bool
|
||||||
|
{
|
||||||
|
return !empty($this->customers);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,11 +9,10 @@
|
|||||||
|
|
||||||
namespace App\Repository\Query;
|
namespace App\Repository\Query;
|
||||||
|
|
||||||
use App\Entity\Customer;
|
|
||||||
|
|
||||||
class ProjectQuery extends BaseQuery implements VisibilityInterface
|
class ProjectQuery extends BaseQuery implements VisibilityInterface
|
||||||
{
|
{
|
||||||
use VisibilityTrait;
|
use VisibilityTrait;
|
||||||
|
use CustomerTrait;
|
||||||
|
|
||||||
public const PROJECT_ORDER_ALLOWED = [
|
public const PROJECT_ORDER_ALLOWED = [
|
||||||
'name',
|
'name',
|
||||||
@@ -29,13 +28,17 @@ class ProjectQuery extends BaseQuery implements VisibilityInterface
|
|||||||
'visible'
|
'visible'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<Customer>
|
|
||||||
*/
|
|
||||||
private array $customers = [];
|
|
||||||
private ?\DateTime $projectStart = null;
|
private ?\DateTime $projectStart = null;
|
||||||
private ?\DateTime $projectEnd = null;
|
private ?\DateTime $projectEnd = null;
|
||||||
private ?bool $globalActivities = null;
|
private ?bool $globalActivities = null;
|
||||||
|
/**
|
||||||
|
* @var array<int>
|
||||||
|
*/
|
||||||
|
private array $projectIds = [];
|
||||||
|
/**
|
||||||
|
* @var array<ProjectQueryHydrate>
|
||||||
|
*/
|
||||||
|
private array $hydrate = [];
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -46,50 +49,27 @@ class ProjectQuery extends BaseQuery implements VisibilityInterface
|
|||||||
'projectEnd' => null,
|
'projectEnd' => null,
|
||||||
'visibility' => VisibilityInterface::SHOW_VISIBLE,
|
'visibility' => VisibilityInterface::SHOW_VISIBLE,
|
||||||
'globalActivities' => null,
|
'globalActivities' => null,
|
||||||
|
'projectIds' => [],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addCustomer(Customer $customer): self
|
protected function copyFrom(BaseQuery $query): void
|
||||||
{
|
{
|
||||||
$this->customers[] = $customer;
|
parent::copyFrom($query);
|
||||||
|
|
||||||
return $this;
|
if (method_exists($query, 'getCustomers')) {
|
||||||
|
$this->setCustomers($query->getCustomers());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if ($query instanceof ProjectQuery) {
|
||||||
* @param array<Customer> $customers
|
$this->setProjectIds($query->getProjectIds());
|
||||||
* @return $this
|
$this->setProjectStart($query->getProjectStart());
|
||||||
*/
|
$this->setProjectEnd($query->getProjectEnd());
|
||||||
public function setCustomers(array $customers): self
|
$this->setGlobalActivities($query->getGlobalActivities());
|
||||||
{
|
foreach ($query->getHydrate() as $hydrate) {
|
||||||
$this->customers = $customers;
|
$this->addHydrate($hydrate);
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<Customer>
|
|
||||||
*/
|
|
||||||
public function getCustomers(): array
|
|
||||||
{
|
|
||||||
return $this->customers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<int>
|
|
||||||
*/
|
|
||||||
public function getCustomerIds(): array
|
|
||||||
{
|
|
||||||
return array_filter(array_values(array_unique(array_map(function (Customer $customer) {
|
|
||||||
return $customer->getId();
|
|
||||||
}, $this->customers))), function ($id) {
|
|
||||||
return $id !== null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function hasCustomers(): bool
|
|
||||||
{
|
|
||||||
return !empty($this->customers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getProjectStart(): ?\DateTime
|
public function getProjectStart(): ?\DateTime
|
||||||
@@ -125,4 +105,40 @@ class ProjectQuery extends BaseQuery implements VisibilityInterface
|
|||||||
{
|
{
|
||||||
$this->globalActivities = $globalActivities;
|
$this->globalActivities = $globalActivities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int> $ids
|
||||||
|
*/
|
||||||
|
public function setProjectIds(array $ids): void
|
||||||
|
{
|
||||||
|
$this->projectIds = $ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
public function getProjectIds(): array
|
||||||
|
{
|
||||||
|
return $this->projectIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addHydrate(ProjectQueryHydrate $hydrate): void
|
||||||
|
{
|
||||||
|
if (!\in_array($hydrate, $this->hydrate, true)) {
|
||||||
|
$this->hydrate[] = $hydrate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ProjectQueryHydrate[]
|
||||||
|
*/
|
||||||
|
public function getHydrate(): array
|
||||||
|
{
|
||||||
|
return $this->hydrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadTeams(): void
|
||||||
|
{
|
||||||
|
$this->addHydrate(ProjectQueryHydrate::TEAMS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/Repository/Query/ProjectQueryHydrate.php
Normal file
16
src/Repository/Query/ProjectQueryHydrate.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Kimai time-tracking app.
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Repository\Query;
|
||||||
|
|
||||||
|
enum ProjectQueryHydrate
|
||||||
|
{
|
||||||
|
case TEAMS;
|
||||||
|
case TEAM_MEMBER;
|
||||||
|
}
|
||||||
@@ -59,6 +59,17 @@ class TimesheetQuery extends ActivityQuery implements BillableInterface, DateRan
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function copyFrom(BaseQuery $query): void
|
||||||
|
{
|
||||||
|
parent::copyFrom($query);
|
||||||
|
|
||||||
|
if ($query instanceof TimesheetQuery) {
|
||||||
|
foreach ($this->getUsers() as $user) {
|
||||||
|
$this->addUser($user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getMaxResults(): ?int
|
public function getMaxResults(): ?int
|
||||||
{
|
{
|
||||||
return $this->maxResults;
|
return $this->maxResults;
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ class UserQuery extends BaseQuery implements VisibilityInterface
|
|||||||
*/
|
*/
|
||||||
private array $searchTeams = [];
|
private array $searchTeams = [];
|
||||||
private ?bool $systemAccount = null;
|
private ?bool $systemAccount = null;
|
||||||
|
/**
|
||||||
|
* @var array<int>
|
||||||
|
*/
|
||||||
|
private array $userIds = [];
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -34,6 +38,7 @@ class UserQuery extends BaseQuery implements VisibilityInterface
|
|||||||
'searchTeams' => [],
|
'searchTeams' => [],
|
||||||
'visibility' => VisibilityInterface::SHOW_VISIBLE,
|
'visibility' => VisibilityInterface::SHOW_VISIBLE,
|
||||||
'systemAccount' => null,
|
'systemAccount' => null,
|
||||||
|
'userIds' => [],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,4 +79,20 @@ class UserQuery extends BaseQuery implements VisibilityInterface
|
|||||||
{
|
{
|
||||||
$this->systemAccount = $systemAccount;
|
$this->systemAccount = $systemAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int> $ids
|
||||||
|
*/
|
||||||
|
public function setUserIds(array $ids): void
|
||||||
|
{
|
||||||
|
$this->userIds = $ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
public function getUserIds(): array
|
||||||
|
{
|
||||||
|
return $this->userIds;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,19 @@ namespace App\Repository\Result;
|
|||||||
|
|
||||||
use App\Entity\Timesheet;
|
use App\Entity\Timesheet;
|
||||||
use App\Repository\Loader\TimesheetLoader;
|
use App\Repository\Loader\TimesheetLoader;
|
||||||
use App\Repository\Paginator\LoaderPaginator;
|
use App\Repository\Paginator\LoaderQueryPaginator;
|
||||||
use App\Repository\Query\TimesheetQuery;
|
use App\Repository\Query\TimesheetQuery;
|
||||||
use App\Utils\Pagination;
|
use App\Utils\Pagination;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
final class TimesheetResult
|
final class TimesheetResult
|
||||||
{
|
{
|
||||||
private ?TimesheetResultStatistic $statisticCache = null;
|
private ?TimesheetResultStatistic $statisticCache = null;
|
||||||
private bool $cachedFullyHydrated = false;
|
|
||||||
/**
|
/**
|
||||||
* @var array<Timesheet>|null
|
* @var array<Timesheet>|null
|
||||||
*/
|
*/
|
||||||
@@ -27,16 +31,22 @@ final class TimesheetResult
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
* @param Query<null, Timesheet> $query
|
||||||
*/
|
*/
|
||||||
public function __construct(private TimesheetQuery $query, private QueryBuilder $queryBuilder)
|
public function __construct(
|
||||||
|
private readonly TimesheetQuery $timesheetQuery,
|
||||||
|
private readonly EntityManagerInterface $entityManager,
|
||||||
|
private readonly QueryBuilder $statisticQb,
|
||||||
|
private readonly Query $query
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getStatistic(): TimesheetResultStatistic
|
public function getStatistic(): TimesheetResultStatistic
|
||||||
{
|
{
|
||||||
if ($this->statisticCache === null) {
|
if ($this->statisticCache === null) {
|
||||||
$withDuration = $this->query->countFilter() > 0;
|
$withDuration = $this->timesheetQuery->countFilter() > 0;
|
||||||
$qb = clone $this->queryBuilder;
|
$qb = clone $this->statisticQb;
|
||||||
$qb
|
$qb
|
||||||
->resetDQLPart('select')
|
->resetDQLPart('select')
|
||||||
->resetDQLPart('orderBy')
|
->resetDQLPart('orderBy')
|
||||||
@@ -47,6 +57,7 @@ final class TimesheetResult
|
|||||||
$qb->addSelect('COALESCE(SUM(t.duration), 0) as duration');
|
$qb->addSelect('COALESCE(SUM(t.duration), 0) as duration');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @var array{'duration': int<0, max>, 'counter': int<0, max>} $result */
|
||||||
$result = $qb->getQuery()->getArrayResult()[0];
|
$result = $qb->getQuery()->getArrayResult()[0];
|
||||||
$duration = $withDuration ? $result['duration'] : 0;
|
$duration = $withDuration ? $result['duration'] : 0;
|
||||||
|
|
||||||
@@ -58,39 +69,34 @@ final class TimesheetResult
|
|||||||
|
|
||||||
public function toIterable(): iterable
|
public function toIterable(): iterable
|
||||||
{
|
{
|
||||||
$query = $this->queryBuilder->getQuery();
|
return $this->query->toIterable();
|
||||||
|
|
||||||
return $query->toIterable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bool $fullyHydrated
|
|
||||||
* @return array<Timesheet>
|
* @return array<Timesheet>
|
||||||
*/
|
*/
|
||||||
public function getResults(bool $fullyHydrated = false): array
|
public function getResults(): array
|
||||||
{
|
{
|
||||||
if ($this->resultCache === null || ($fullyHydrated && $this->cachedFullyHydrated === false)) {
|
if ($this->resultCache === null) {
|
||||||
$query = $this->queryBuilder->getQuery();
|
/** @var array<Timesheet> $results */
|
||||||
$results = $query->getResult();
|
$results = $this->query->getResult();
|
||||||
|
|
||||||
$loader = new TimesheetLoader($this->queryBuilder->getEntityManager(), $fullyHydrated);
|
$loader = new TimesheetLoader($this->entityManager, true);
|
||||||
$loader->loadResults($results);
|
$loader->loadResults($results);
|
||||||
|
|
||||||
$this->cachedFullyHydrated = $fullyHydrated;
|
|
||||||
$this->resultCache = $results;
|
$this->resultCache = $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->resultCache;
|
return $this->resultCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPagerfanta(bool $fullyHydrated = false): Pagination
|
public function getPagerfanta(): Pagination
|
||||||
{
|
{
|
||||||
$qb = clone $this->queryBuilder;
|
$loader = new LoaderQueryPaginator(new TimesheetLoader($this->entityManager), $this->query, $this->getStatistic()->getCount());
|
||||||
|
|
||||||
$loader = new LoaderPaginator(new TimesheetLoader($qb->getEntityManager(), $fullyHydrated), $qb, $this->getStatistic()->getCount());
|
|
||||||
$paginator = new Pagination($loader);
|
$paginator = new Pagination($loader);
|
||||||
$paginator->setMaxPerPage($this->query->getPageSize());
|
$paginator->setMaxPerPage($this->timesheetQuery->getPageSize());
|
||||||
$paginator->setCurrentPage($this->query->getPage());
|
$paginator->setCurrentPage($this->timesheetQuery->getPage());
|
||||||
|
|
||||||
return $paginator;
|
return $paginator;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,17 @@ namespace App\Repository\Result;
|
|||||||
|
|
||||||
final class TimesheetResultStatistic
|
final class TimesheetResultStatistic
|
||||||
{
|
{
|
||||||
public function __construct(private int $count, private int $duration)
|
/**
|
||||||
|
* @param int<0, max> $count
|
||||||
|
* @param int<0, max> $duration
|
||||||
|
*/
|
||||||
|
public function __construct(private readonly int $count, private readonly int $duration)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int<0, max>
|
||||||
|
*/
|
||||||
public function getCount(): int
|
public function getCount(): int
|
||||||
{
|
{
|
||||||
return $this->count;
|
return $this->count;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
use App\Entity\Tag;
|
use App\Entity\Tag;
|
||||||
use App\Repository\Paginator\QueryBuilderPaginator;
|
use App\Repository\Paginator\QueryPaginator;
|
||||||
use App\Repository\Query\TagFormTypeQuery;
|
use App\Repository\Query\TagFormTypeQuery;
|
||||||
use App\Repository\Query\TagQuery;
|
use App\Repository\Query\TagQuery;
|
||||||
use App\Utils\Pagination;
|
use App\Utils\Pagination;
|
||||||
@@ -115,9 +115,10 @@ class TagRepository extends EntityRepository
|
|||||||
->resetDQLPart('orderBy')
|
->resetDQLPart('orderBy')
|
||||||
->select($qb->expr()->count('tag.id'))
|
->select($qb->expr()->count('tag.id'))
|
||||||
;
|
;
|
||||||
|
/** @var int<0, max> $counter */
|
||||||
$counter = (int) $qb->getQuery()->getSingleScalarResult();
|
$counter = (int) $qb->getQuery()->getSingleScalarResult();
|
||||||
|
|
||||||
$paginator = new QueryBuilderPaginator($qb1, $counter);
|
$paginator = new QueryPaginator($qb1->getQuery(), $counter);
|
||||||
|
|
||||||
$pager = new Pagination($paginator);
|
$pager = new Pagination($paginator);
|
||||||
$pager->setMaxPerPage($query->getPageSize());
|
$pager->setMaxPerPage($query->getPageSize());
|
||||||
|
|||||||
@@ -11,83 +11,62 @@ namespace App\Repository;
|
|||||||
|
|
||||||
use App\Entity\Team;
|
use App\Entity\Team;
|
||||||
use App\Entity\TeamMember;
|
use App\Entity\TeamMember;
|
||||||
use App\Entity\Timesheet;
|
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Repository\Loader\TeamLoader;
|
use App\Repository\Loader\TeamLoader;
|
||||||
use App\Repository\Paginator\LoaderPaginator;
|
use App\Repository\Paginator\LoaderQueryPaginator;
|
||||||
use App\Repository\Paginator\PaginatorInterface;
|
use App\Repository\Paginator\PaginatorInterface;
|
||||||
use App\Repository\Query\TeamQuery;
|
use App\Repository\Query\TeamQuery;
|
||||||
use App\Utils\Pagination;
|
use App\Utils\Pagination;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\ORM\Exception\ORMException;
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @extends \Doctrine\ORM\EntityRepository<Team>
|
* @extends EntityRepository<Team>
|
||||||
*/
|
*/
|
||||||
class TeamRepository extends EntityRepository
|
class TeamRepository extends EntityRepository
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @return Team[]
|
|
||||||
*/
|
|
||||||
public function findAll(): array
|
|
||||||
{
|
|
||||||
$result = parent::findAll();
|
|
||||||
|
|
||||||
$loader = new TeamLoader($this->getEntityManager());
|
|
||||||
$loader->loadResults($result);
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int[] $teamIds
|
* @param int[] $teamIds
|
||||||
* @return Team[]
|
* @return Team[]
|
||||||
*/
|
*/
|
||||||
public function findByIds(array $teamIds): array
|
public function findByIds(array $teamIds): array
|
||||||
{
|
{
|
||||||
|
$ids = array_filter(
|
||||||
|
array_unique($teamIds),
|
||||||
|
function ($value) {
|
||||||
|
return $value > 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (\count($ids) === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
$qb = $this->createQueryBuilder('t');
|
$qb = $this->createQueryBuilder('t');
|
||||||
$qb
|
$qb
|
||||||
->where($qb->expr()->in('t.id', ':id'))
|
->where($qb->expr()->in('t.id', ':id'))
|
||||||
->setParameter('id', $teamIds)
|
->setParameter('id', $ids)
|
||||||
;
|
;
|
||||||
|
|
||||||
$teams = $qb->getQuery()->getResult();
|
return $this->getTeams($this->prepareTeamQuery($qb->getQuery()));
|
||||||
|
|
||||||
$loader = new TeamLoader($qb->getEntityManager());
|
|
||||||
$loader->loadResults($teams);
|
|
||||||
|
|
||||||
return $teams;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function saveTeam(Team $team): void
|
||||||
* @param Team $team
|
|
||||||
* @throws ORMException
|
|
||||||
* @throws \Doctrine\ORM\OptimisticLockException
|
|
||||||
*/
|
|
||||||
public function saveTeam(Team $team)
|
|
||||||
{
|
{
|
||||||
$entityManager = $this->getEntityManager();
|
$entityManager = $this->getEntityManager();
|
||||||
$entityManager->persist($team);
|
$entityManager->persist($team);
|
||||||
$entityManager->flush();
|
$entityManager->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function removeTeamMember(TeamMember $member): void
|
||||||
* @param TeamMember $member
|
|
||||||
* @throws ORMException
|
|
||||||
*/
|
|
||||||
public function removeTeamMember(TeamMember $member)
|
|
||||||
{
|
{
|
||||||
$entityManager = $this->getEntityManager();
|
$entityManager = $this->getEntityManager();
|
||||||
$entityManager->remove($member);
|
$entityManager->remove($member);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function deleteTeam(Team $team): void
|
||||||
* @param Team $team
|
|
||||||
* @throws ORMException
|
|
||||||
* @throws \Doctrine\ORM\OptimisticLockException
|
|
||||||
*/
|
|
||||||
public function deleteTeam(Team $team)
|
|
||||||
{
|
{
|
||||||
$entityManager = $this->getEntityManager();
|
$entityManager = $this->getEntityManager();
|
||||||
$entityManager->remove($team);
|
$entityManager->remove($team);
|
||||||
@@ -96,9 +75,6 @@ class TeamRepository extends EntityRepository
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a query builder that is used for TeamType and your own 'query_builder' option.
|
* Returns a query builder that is used for TeamType and your own 'query_builder' option.
|
||||||
*
|
|
||||||
* @param TeamQuery $query
|
|
||||||
* @return QueryBuilder
|
|
||||||
*/
|
*/
|
||||||
public function getQueryBuilderForFormType(TeamQuery $query): QueryBuilder
|
public function getQueryBuilderForFormType(TeamQuery $query): QueryBuilder
|
||||||
{
|
{
|
||||||
@@ -118,32 +94,46 @@ class TeamRepository extends EntityRepository
|
|||||||
return new Pagination($this->getPaginatorForQuery($query), $query);
|
return new Pagination($this->getPaginatorForQuery($query), $query);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPaginatorForQuery(TeamQuery $query): PaginatorInterface
|
/**
|
||||||
|
* @return PaginatorInterface<Team>
|
||||||
|
*/
|
||||||
|
private function getPaginatorForQuery(TeamQuery $teamQuery): PaginatorInterface
|
||||||
{
|
{
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$qb = $this->getQueryBuilderForQuery($teamQuery);
|
||||||
$qb
|
$qb
|
||||||
->resetDQLPart('select')
|
->resetDQLPart('select')
|
||||||
->resetDQLPart('orderBy')
|
->resetDQLPart('orderBy')
|
||||||
->select($qb->expr()->countDistinct('t.id'))
|
->select($qb->expr()->countDistinct('t.id'))
|
||||||
;
|
;
|
||||||
|
/** @var int<0, max> $counter */
|
||||||
$counter = (int) $qb->getQuery()->getSingleScalarResult();
|
$counter = (int) $qb->getQuery()->getSingleScalarResult();
|
||||||
|
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$query = $this->createTeamQuery($teamQuery);
|
||||||
|
|
||||||
return new LoaderPaginator(new TeamLoader($qb->getEntityManager()), $qb, $counter);
|
return new LoaderQueryPaginator(new TeamLoader($qb->getEntityManager()), $query, $counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param TeamQuery $query
|
* @return Team[]
|
||||||
* @return Timesheet[]
|
|
||||||
*/
|
*/
|
||||||
public function getTeamsForQuery(TeamQuery $query): iterable
|
public function getTeamsForQuery(TeamQuery $query): iterable
|
||||||
{
|
{
|
||||||
// this is using the paginator internally, as it will load all joined entities into the working unit
|
return $this->getTeams($this->createTeamQuery($query));
|
||||||
// do not "optimize" to use the query directly, as it would results in hundreds of additional lazy queries
|
}
|
||||||
$paginator = $this->getPaginatorForQuery($query);
|
|
||||||
|
|
||||||
return $paginator->getAll();
|
/**
|
||||||
|
* @param Query<Team> $query
|
||||||
|
* @return Team[]
|
||||||
|
*/
|
||||||
|
public function getTeams(Query $query): array
|
||||||
|
{
|
||||||
|
/** @var array<Team> $teams */
|
||||||
|
$teams = $query->execute();
|
||||||
|
|
||||||
|
$loader = new TeamLoader($this->getEntityManager());
|
||||||
|
$loader->loadResults($teams);
|
||||||
|
|
||||||
|
return $teams;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getQueryBuilderForQuery(TeamQuery $query): QueryBuilder
|
private function getQueryBuilderForQuery(TeamQuery $query): QueryBuilder
|
||||||
@@ -208,11 +198,9 @@ class TeamRepository extends EntityRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param QueryBuilder $qb
|
|
||||||
* @param User|null $user
|
|
||||||
* @param Team[] $teams
|
* @param Team[] $teams
|
||||||
*/
|
*/
|
||||||
private function addPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = [])
|
private function addPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = []): void
|
||||||
{
|
{
|
||||||
// make sure that all queries without a user see all user
|
// make sure that all queries without a user see all user
|
||||||
if (null === $user && empty($teams)) {
|
if (null === $user && empty($teams)) {
|
||||||
@@ -228,7 +216,7 @@ class TeamRepository extends EntityRepository
|
|||||||
// OR we query for all teams where the user is a member - in later case $teams is not empty
|
// OR we query for all teams where the user is a member - in later case $teams is not empty
|
||||||
$or = $qb->expr()->orX();
|
$or = $qb->expr()->orX();
|
||||||
|
|
||||||
// this query should limit to teams where the user is a teamlead (eg. in dropdowns or listing page)
|
// this query should limit to teams where the user is a teamlead (e.g. in dropdowns or listing page)
|
||||||
if (null !== $user) {
|
if (null !== $user) {
|
||||||
$qb->leftJoin('t.members', 'members');
|
$qb->leftJoin('t.members', 'members');
|
||||||
$or->add(
|
$or->add(
|
||||||
@@ -255,4 +243,31 @@ class TeamRepository extends EntityRepository
|
|||||||
$qb->andWhere($or);
|
$qb->andWhere($or);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Query<Team>
|
||||||
|
*/
|
||||||
|
private function createTeamQuery(TeamQuery $teamQuery): Query
|
||||||
|
{
|
||||||
|
$query = $this->getQueryBuilderForQuery($teamQuery)->getQuery();
|
||||||
|
$query = $this->prepareTeamQuery($query);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Query<Team> $query
|
||||||
|
* @return Query<Team>
|
||||||
|
*/
|
||||||
|
public function prepareTeamQuery(Query $query): Query
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->getConfiguration()->setEagerFetchBatchSize(300);
|
||||||
|
|
||||||
|
// $query->setFetchMode(Team::class, 'members', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(Team::class, 'customers', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(Team::class, 'projects', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(Team::class, 'activities', ClassMetadata::FETCH_EAGER);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ use App\Entity\User;
|
|||||||
use App\Model\Revenue;
|
use App\Model\Revenue;
|
||||||
use App\Model\TimesheetStatistic;
|
use App\Model\TimesheetStatistic;
|
||||||
use App\Repository\Loader\TimesheetLoader;
|
use App\Repository\Loader\TimesheetLoader;
|
||||||
use App\Repository\Paginator\LoaderPaginator;
|
use App\Repository\Paginator\LoaderQueryPaginator;
|
||||||
use App\Repository\Paginator\PaginatorInterface;
|
use App\Repository\Paginator\PaginatorInterface;
|
||||||
use App\Repository\Query\TimesheetQuery;
|
use App\Repository\Query\TimesheetQuery;
|
||||||
use App\Repository\Result\TimesheetResult;
|
use App\Repository\Result\TimesheetResult;
|
||||||
@@ -30,13 +30,15 @@ use DateInterval;
|
|||||||
use DateTime;
|
use DateTime;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\Query\Expr\Join;
|
use Doctrine\ORM\Query\Expr\Join;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Exception;
|
use Exception;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @extends \Doctrine\ORM\EntityRepository<Timesheet>
|
* @extends EntityRepository<Timesheet>
|
||||||
*/
|
*/
|
||||||
class TimesheetRepository extends EntityRepository
|
class TimesheetRepository extends EntityRepository
|
||||||
{
|
{
|
||||||
@@ -230,7 +232,6 @@ class TimesheetRepository extends EntityRepository
|
|||||||
/**
|
/**
|
||||||
* @param string|string[] $select
|
* @param string|string[] $select
|
||||||
* @return int|mixed
|
* @return int|mixed
|
||||||
* @throws \Doctrine\ORM\NonUniqueResultException
|
|
||||||
*/
|
*/
|
||||||
private function queryTimeRange(string|array $select, ?\DateTimeInterface $begin, ?\DateTimeInterface $end, ?User $user, ?bool $billable = null): mixed
|
private function queryTimeRange(string|array $select, ?\DateTimeInterface $begin, ?\DateTimeInterface $end, ?User $user, ?bool $billable = null): mixed
|
||||||
{
|
{
|
||||||
@@ -346,9 +347,12 @@ class TimesheetRepository extends EntityRepository
|
|||||||
return $qb->getQuery()->getResult();
|
return $qb->getQuery()->getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getHydratedResultsByQuery($qb, false);
|
return $this->getHydratedResultsByQuery($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int<0, max>
|
||||||
|
*/
|
||||||
public function countActiveEntries(?User $user = null): int
|
public function countActiveEntries(?User $user = null): int
|
||||||
{
|
{
|
||||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||||
@@ -366,9 +370,12 @@ class TimesheetRepository extends EntityRepository
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
return (int) $qb->getQuery()->getSingleScalarResult(); // @phpstan-ignore-line
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int<0, max>
|
||||||
|
*/
|
||||||
public function countActiveUsers(?\DateTimeInterface $begin, ?\DateTimeInterface $end, ?bool $billable = null): int
|
public function countActiveUsers(?\DateTimeInterface $begin, ?\DateTimeInterface $end, ?bool $billable = null): int
|
||||||
{
|
{
|
||||||
$tmp = $this->queryTimeRange('COUNT(DISTINCT(t.user))', $begin, $end, null, $billable);
|
$tmp = $this->queryTimeRange('COUNT(DISTINCT(t.user))', $begin, $end, null, $billable);
|
||||||
@@ -377,7 +384,7 @@ class TimesheetRepository extends EntityRepository
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int) $tmp;
|
return (int) $tmp; // @phpstan-ignore-line
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -442,7 +449,10 @@ class TimesheetRepository extends EntityRepository
|
|||||||
return new Pagination($this->getPaginatorForQuery($query), $query);
|
return new Pagination($this->getPaginatorForQuery($query), $query);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getPaginatorForQuery(TimesheetQuery $query): PaginatorInterface
|
/**
|
||||||
|
* @return int<0, max>
|
||||||
|
*/
|
||||||
|
private function countTimesheetsForQuery(TimesheetQuery $query): int
|
||||||
{
|
{
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$qb = $this->getQueryBuilderForQuery($query);
|
||||||
$qb
|
$qb
|
||||||
@@ -450,50 +460,60 @@ class TimesheetRepository extends EntityRepository
|
|||||||
->resetDQLPart('orderBy')
|
->resetDQLPart('orderBy')
|
||||||
->select($qb->expr()->count('t.id'))
|
->select($qb->expr()->count('t.id'))
|
||||||
;
|
;
|
||||||
$counter = (int) $qb->getQuery()->getSingleScalarResult();
|
|
||||||
|
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
return (int) $qb->getQuery()->getSingleScalarResult(); // @phpstan-ignore-line
|
||||||
|
}
|
||||||
|
|
||||||
return new LoaderPaginator(new TimesheetLoader($qb->getEntityManager()), $qb, $counter);
|
/**
|
||||||
|
* @return PaginatorInterface<Timesheet>
|
||||||
|
*/
|
||||||
|
private function getPaginatorForQuery(TimesheetQuery $timesheetQuery): PaginatorInterface
|
||||||
|
{
|
||||||
|
$counter = $this->countTimesheetsForQuery($timesheetQuery);
|
||||||
|
$query = $this->createTimesheetQuery($timesheetQuery);
|
||||||
|
|
||||||
|
return new LoaderQueryPaginator(new TimesheetLoader($this->getEntityManager()), $query, $counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When switching $fullyHydrated to true, the call gets even more expensive.
|
* When switching $fullyHydrated to true, the call gets even more expensive.
|
||||||
* You normally don't need this, unless you want to access deeply nested attributes for many entries.
|
* You normally don't need this, unless you want to access deeply nested attributes for many entries.
|
||||||
*
|
*
|
||||||
* @param TimesheetQuery $query
|
|
||||||
* @param bool $fullyHydrated
|
|
||||||
* @param bool $basicHydrated
|
|
||||||
* @return Timesheet[]
|
* @return Timesheet[]
|
||||||
*/
|
*/
|
||||||
public function getTimesheetsForQuery(TimesheetQuery $query, bool $fullyHydrated = false, bool $basicHydrated = true): iterable
|
public function getTimesheetsForQuery(TimesheetQuery $query, bool $fullyHydrated = false): array
|
||||||
{
|
{
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$qb = $this->getQueryBuilderForQuery($query);
|
||||||
|
|
||||||
return $this->getHydratedResultsByQuery($qb, $fullyHydrated, $basicHydrated);
|
return $this->getHydratedResultsByQuery($qb, $fullyHydrated);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTimesheetResult(TimesheetQuery $query): TimesheetResult
|
public function getTimesheetResult(TimesheetQuery $query): TimesheetResult
|
||||||
{
|
{
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
return new TimesheetResult(
|
||||||
|
$query,
|
||||||
return new TimesheetResult($query, $qb);
|
$this->getEntityManager(),
|
||||||
|
$this->getQueryBuilderForQuery($query),
|
||||||
|
$this->createTimesheetQuery($query)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param QueryBuilder $qb
|
|
||||||
* @param bool $fullyHydrated
|
|
||||||
* @param bool $basicHydrated
|
|
||||||
* @return Timesheet[]
|
* @return Timesheet[]
|
||||||
*/
|
*/
|
||||||
private function getHydratedResultsByQuery(QueryBuilder $qb, bool $fullyHydrated = false, bool $basicHydrated = true): iterable
|
private function getHydratedResultsByQuery(QueryBuilder $qb, bool $fullyHydrated = false): array
|
||||||
{
|
{
|
||||||
$results = $qb->getQuery()->getResult();
|
/** @var Query<Timesheet> $query */
|
||||||
|
$query = $qb->getQuery();
|
||||||
|
$query = $this->prepareTimesheetQuery($query);
|
||||||
|
|
||||||
$loader = new TimesheetLoader($qb->getEntityManager(), $fullyHydrated, $basicHydrated);
|
/** @var array<Timesheet> $timesheets */
|
||||||
$loader->loadResults($results);
|
$timesheets = $query->getResult();
|
||||||
|
|
||||||
return $results;
|
$loader = new TimesheetLoader($qb->getEntityManager(), $fullyHydrated);
|
||||||
|
$loader->loadResults($timesheets);
|
||||||
|
|
||||||
|
return $timesheets;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getQueryBuilderForQuery(TimesheetQuery $query): QueryBuilder
|
private function getQueryBuilderForQuery(TimesheetQuery $query): QueryBuilder
|
||||||
@@ -660,12 +680,9 @@ class TimesheetRepository extends EntityRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param User $user
|
|
||||||
* @param DateTime|null $startFrom
|
|
||||||
* @param int $limit
|
|
||||||
* @return Timesheet[]
|
* @return Timesheet[]
|
||||||
*/
|
*/
|
||||||
public function getRecentActivities(User $user, DateTime $startFrom = null, int $limit = 10): array
|
public function getRecentActivities(User $user, ?\DateTimeInterface $startFrom = null, int $limit = 10): array
|
||||||
{
|
{
|
||||||
return $this->findTimesheetsById(
|
return $this->findTimesheetsById(
|
||||||
$user,
|
$user,
|
||||||
@@ -674,12 +691,9 @@ class TimesheetRepository extends EntityRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param User $user
|
|
||||||
* @param DateTime|null $startFrom
|
|
||||||
* @param int $limit
|
|
||||||
* @return array<int>
|
* @return array<int>
|
||||||
*/
|
*/
|
||||||
public function getRecentActivityIds(User $user, DateTime $startFrom = null, int $limit = 10): array
|
public function getRecentActivityIds(User $user, ?\DateTimeInterface $startFrom = null, int $limit = 10): array
|
||||||
{
|
{
|
||||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||||
|
|
||||||
@@ -707,7 +721,7 @@ class TimesheetRepository extends EntityRepository
|
|||||||
|
|
||||||
if (null !== $startFrom) {
|
if (null !== $startFrom) {
|
||||||
$qb->andWhere($qb->expr()->gte('t.begin', ':begin'))
|
$qb->andWhere($qb->expr()->gte('t.begin', ':begin'))
|
||||||
->setParameter('begin', $startFrom);
|
->setParameter('begin', \DateTimeImmutable::createFromInterface($startFrom), Types::DATETIME_IMMUTABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
$qb->join('t.project', 'p');
|
$qb->join('t.project', 'p');
|
||||||
@@ -725,13 +739,10 @@ class TimesheetRepository extends EntityRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param User $user
|
|
||||||
* @param array<int> $ids
|
* @param array<int> $ids
|
||||||
* @param bool $fullyHydrated
|
|
||||||
* @param bool $basicHydrated
|
|
||||||
* @return array<Timesheet>
|
* @return array<Timesheet>
|
||||||
*/
|
*/
|
||||||
public function findTimesheetsById(User $user, array $ids, bool $fullyHydrated = false, bool $basicHydrated = true): array
|
public function findTimesheetsById(User $user, array $ids): array
|
||||||
{
|
{
|
||||||
if (\count($ids) === 0) {
|
if (\count($ids) === 0) {
|
||||||
return [];
|
return [];
|
||||||
@@ -749,13 +760,13 @@ class TimesheetRepository extends EntityRepository
|
|||||||
|
|
||||||
$this->addPermissionCriteria($qb, $user);
|
$this->addPermissionCriteria($qb, $user);
|
||||||
|
|
||||||
return $this->getHydratedResultsByQuery($qb, $fullyHydrated, $basicHydrated);
|
return $this->getHydratedResultsByQuery($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Timesheet[]|int[] $timesheets
|
* @param Timesheet[]|int[] $timesheets
|
||||||
*/
|
*/
|
||||||
public function setExported(array $timesheets)
|
public function setExported(array $timesheets): void
|
||||||
{
|
{
|
||||||
$em = $this->getEntityManager();
|
$em = $this->getEntityManager();
|
||||||
$em->beginTransaction();
|
$em->beginTransaction();
|
||||||
@@ -896,4 +907,40 @@ class TimesheetRepository extends EntityRepository
|
|||||||
|
|
||||||
return $result > 0;
|
return $result > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Query<Timesheet>
|
||||||
|
*/
|
||||||
|
private function createTimesheetQuery(TimesheetQuery $timesheetQuery): Query
|
||||||
|
{
|
||||||
|
$query = $this->getQueryBuilderForQuery($timesheetQuery)->getQuery();
|
||||||
|
$query = $this->prepareTimesheetQuery($query);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Query<Timesheet> $query
|
||||||
|
* @return Query<Timesheet>
|
||||||
|
*/
|
||||||
|
public function prepareTimesheetQuery(Query $query): Query
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->getConfiguration()->setEagerFetchBatchSize(300);
|
||||||
|
|
||||||
|
$query->setFetchMode(Timesheet::class, 'meta', ClassMetadata::FETCH_EAGER);
|
||||||
|
$query->setFetchMode(Timesheet::class, 'activity', ClassMetadata::FETCH_EAGER);
|
||||||
|
$query->setFetchMode(Timesheet::class, 'project', ClassMetadata::FETCH_EAGER);
|
||||||
|
$query->setFetchMode(Timesheet::class, 'user', ClassMetadata::FETCH_EAGER);
|
||||||
|
|
||||||
|
// not yet supported by Doctrine
|
||||||
|
// $query->setFetchMode(Activity::class, 'meta', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(Project::class, 'customer', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(Project::class, 'meta', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(Customer::class, 'meta', ClassMetadata::FETCH_EAGER);
|
||||||
|
|
||||||
|
// ManyToMany not supported by Doctrine yet
|
||||||
|
// $query->setFetchMode(Timesheet::class, 'tags', ClassMetadata::FETCH_EAGER);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,15 +15,16 @@ use App\Entity\Timesheet;
|
|||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Entity\UserPreference;
|
use App\Entity\UserPreference;
|
||||||
use App\Repository\Loader\UserLoader;
|
use App\Repository\Loader\UserLoader;
|
||||||
use App\Repository\Paginator\LoaderPaginator;
|
use App\Repository\Paginator\LoaderQueryPaginator;
|
||||||
use App\Repository\Paginator\PaginatorInterface;
|
use App\Repository\Paginator\PaginatorInterface;
|
||||||
use App\Repository\Query\UserFormTypeQuery;
|
use App\Repository\Query\UserFormTypeQuery;
|
||||||
use App\Repository\Query\UserQuery;
|
use App\Repository\Query\UserQuery;
|
||||||
|
use App\Repository\Query\VisibilityInterface;
|
||||||
use App\Utils\Pagination;
|
use App\Utils\Pagination;
|
||||||
use Doctrine\DBAL\ParameterType;
|
use Doctrine\DBAL\ParameterType;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\EntityRepository;
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\ORM\Exception\ORMException;
|
use Doctrine\ORM\Query;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
|
use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface;
|
||||||
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
use Symfony\Component\Security\Core\Exception\UserNotFoundException;
|
||||||
@@ -33,7 +34,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
|
|||||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @extends \Doctrine\ORM\EntityRepository<User>
|
* @extends EntityRepository<User>
|
||||||
* @template-implements PasswordUpgraderInterface<User>
|
* @template-implements PasswordUpgraderInterface<User>
|
||||||
* @template-implements UserProviderInterface<User>
|
* @template-implements UserProviderInterface<User>
|
||||||
*/
|
*/
|
||||||
@@ -48,11 +49,6 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param User $user
|
|
||||||
* @throws ORMException
|
|
||||||
* @throws \Doctrine\ORM\OptimisticLockException
|
|
||||||
*/
|
|
||||||
public function saveUser(User $user): void
|
public function saveUser(User $user): void
|
||||||
{
|
{
|
||||||
$entityManager = $this->getEntityManager();
|
$entityManager = $this->getEntityManager();
|
||||||
@@ -78,23 +74,15 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getUserById(int $id): ?User
|
||||||
* Used to fetch a user by its ID.
|
|
||||||
*
|
|
||||||
* @param int $id
|
|
||||||
* @return null|User
|
|
||||||
*/
|
|
||||||
public function getUserById($id): ?User
|
|
||||||
{
|
{
|
||||||
/** @var User|null $user */
|
$users = $this->findByIds([$id]);
|
||||||
$user = $this->find($id);
|
|
||||||
|
|
||||||
if ($user !== null) {
|
if (\count($users) === 1) {
|
||||||
$loader = new UserLoader($this->getEntityManager(), true);
|
return $users[0];
|
||||||
$loader->loadResults([$user]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $user;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,18 +91,24 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
|||||||
*/
|
*/
|
||||||
public function findByIds(array $userIds): array
|
public function findByIds(array $userIds): array
|
||||||
{
|
{
|
||||||
|
$ids = array_filter(
|
||||||
|
array_unique($userIds),
|
||||||
|
function ($value) {
|
||||||
|
return $value > 0;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (\count($ids) === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
$qb = $this->createQueryBuilder('u');
|
$qb = $this->createQueryBuilder('u');
|
||||||
$qb
|
$qb
|
||||||
->where($qb->expr()->in('u.id', ':id'))
|
->where($qb->expr()->in('u.id', ':id'))
|
||||||
->setParameter('id', $userIds)
|
->setParameter('id', $ids)
|
||||||
;
|
;
|
||||||
|
|
||||||
$users = $qb->getQuery()->getResult();
|
return $this->getUsers($this->prepareUserQuery($qb->getQuery()));
|
||||||
|
|
||||||
$loader = new UserLoader($qb->getEntityManager(), true);
|
|
||||||
$loader->loadResults($users);
|
|
||||||
|
|
||||||
return $users;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,6 +129,9 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
|||||||
return parent::findOneBy(['username' => $username]);
|
return parent::findOneBy(['username' => $username]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int<0, max>
|
||||||
|
*/
|
||||||
public function countUser(?bool $enabled = null): int
|
public function countUser(?bool $enabled = null): int
|
||||||
{
|
{
|
||||||
if (null !== $enabled) {
|
if (null !== $enabled) {
|
||||||
@@ -200,11 +197,9 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param QueryBuilder $qb
|
|
||||||
* @param User|null $user
|
|
||||||
* @param Team[] $teams
|
* @param Team[] $teams
|
||||||
*/
|
*/
|
||||||
private function addPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = [])
|
private function addPermissionCriteria(QueryBuilder $qb, ?User $user = null, array $teams = []): void
|
||||||
{
|
{
|
||||||
// make sure that all queries without a user see all user
|
// make sure that all queries without a user see all user
|
||||||
if (null === $user && empty($teams)) {
|
if (null === $user && empty($teams)) {
|
||||||
@@ -259,25 +254,16 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $role
|
|
||||||
* @return User[]
|
* @return User[]
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
public function findUsersWithRole(string $role): array
|
public function findUsersWithRole(string $role): array
|
||||||
{
|
{
|
||||||
if ($role === User::ROLE_USER) {
|
$query = new UserQuery();
|
||||||
return $this->findAll();
|
$query->setRole($role);
|
||||||
}
|
$query->setVisibility(VisibilityInterface::SHOW_BOTH);
|
||||||
|
|
||||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
return $this->getUsersForQuery($query);
|
||||||
|
|
||||||
$qb
|
|
||||||
->select('u')
|
|
||||||
->from(User::class, 'u')
|
|
||||||
->andWhere('u.roles LIKE :role');
|
|
||||||
$qb->setParameter('role', '%' . $role . '%');
|
|
||||||
|
|
||||||
return $qb->getQuery()->getResult();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getQueryBuilderForQuery(UserQuery $query): QueryBuilder
|
private function getQueryBuilderForQuery(UserQuery $query): QueryBuilder
|
||||||
@@ -377,6 +363,9 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
|||||||
return new Pagination($this->getPaginatorForQuery($query), $query);
|
return new Pagination($this->getPaginatorForQuery($query), $query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int<0, max>
|
||||||
|
*/
|
||||||
public function countUsersForQuery(UserQuery $query): int
|
public function countUsersForQuery(UserQuery $query): int
|
||||||
{
|
{
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$qb = $this->getQueryBuilderForQuery($query);
|
||||||
@@ -386,15 +375,18 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
|||||||
->select($qb->expr()->countDistinct('u.id'))
|
->select($qb->expr()->countDistinct('u.id'))
|
||||||
;
|
;
|
||||||
|
|
||||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
return (int) $qb->getQuery()->getSingleScalarResult(); // @phpstan-ignore-line
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getPaginatorForQuery(UserQuery $query): PaginatorInterface
|
/**
|
||||||
|
* @return PaginatorInterface<User>
|
||||||
|
*/
|
||||||
|
private function getPaginatorForQuery(UserQuery $userQuery): PaginatorInterface
|
||||||
{
|
{
|
||||||
$counter = $this->countUsersForQuery($query);
|
$counter = $this->countUsersForQuery($userQuery);
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
$query = $this->createUserQuery($userQuery);
|
||||||
|
|
||||||
return new LoaderPaginator(new UserLoader($qb->getEntityManager()), $qb, $counter);
|
return new LoaderQueryPaginator(new UserLoader($this->getEntityManager()), $query, $counter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -403,27 +395,25 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
|||||||
*/
|
*/
|
||||||
public function getUsersForQuery(UserQuery $query): array
|
public function getUsersForQuery(UserQuery $query): array
|
||||||
{
|
{
|
||||||
$qb = $this->getQueryBuilderForQuery($query);
|
return $this->getUsers($this->createUserQuery($query));
|
||||||
|
|
||||||
return $this->getHydratedResultsByQuery($qb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param QueryBuilder $qb
|
* @param Query<User> $query
|
||||||
* @return User[]
|
* @return User[]
|
||||||
*/
|
*/
|
||||||
protected function getHydratedResultsByQuery(QueryBuilder $qb): array
|
public function getUsers(Query $query): array
|
||||||
{
|
{
|
||||||
/** @var array<User> $results */
|
/** @var array<User> $users */
|
||||||
$results = $qb->getQuery()->getResult();
|
$users = $query->execute();
|
||||||
|
|
||||||
$loader = new UserLoader($qb->getEntityManager());
|
$loader = new UserLoader($this->getEntityManager());
|
||||||
$loader->loadResults($results);
|
$loader->loadResults($users);
|
||||||
|
|
||||||
return $results;
|
return $users;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteUser(User $delete, ?User $replace = null)
|
public function deleteUser(User $delete, ?User $replace = null): void
|
||||||
{
|
{
|
||||||
$em = $this->getEntityManager();
|
$em = $this->getEntityManager();
|
||||||
$em->beginTransaction();
|
$em->beginTransaction();
|
||||||
@@ -454,9 +444,35 @@ class UserRepository extends EntityRepository implements UserLoaderInterface, Us
|
|||||||
$em->remove($delete);
|
$em->remove($delete);
|
||||||
$em->flush();
|
$em->flush();
|
||||||
$em->commit();
|
$em->commit();
|
||||||
} catch (ORMException $ex) {
|
} catch (\Exception $ex) {
|
||||||
$em->rollback();
|
$em->rollback();
|
||||||
throw $ex;
|
throw $ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Query<User>
|
||||||
|
*/
|
||||||
|
private function createUserQuery(UserQuery $userQuery): Query
|
||||||
|
{
|
||||||
|
$query = $this->getQueryBuilderForQuery($userQuery)->getQuery();
|
||||||
|
$query = $this->prepareUserQuery($query);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Query<User> $query
|
||||||
|
* @return Query<User>
|
||||||
|
*/
|
||||||
|
public function prepareUserQuery(Query $query): Query
|
||||||
|
{
|
||||||
|
$this->getEntityManager()->getConfiguration()->setEagerFetchBatchSize(300);
|
||||||
|
|
||||||
|
// $query->setFetchMode(User::class, 'preferences', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(User::class, 'supervisor', ClassMetadata::FETCH_EAGER);
|
||||||
|
// $query->setFetchMode(User::class, 'memberships', ClassMetadata::FETCH_EAGER);
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ final class FavoriteRecordService
|
|||||||
|
|
||||||
$all = [];
|
$all = [];
|
||||||
if (\count($ids) > 0) {
|
if (\count($ids) > 0) {
|
||||||
$timesheets = $this->repository->findTimesheetsById($user, $ids, false, false);
|
$timesheets = $this->repository->findTimesheetsById($user, $ids);
|
||||||
foreach ($timesheets as $timesheet) {
|
foreach ($timesheets as $timesheet) {
|
||||||
$id = $timesheet->getId();
|
$id = $timesheet->getId();
|
||||||
if ($id === null) {
|
if ($id === null) {
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ final class DataTable implements \Countable, \IteratorAggregate
|
|||||||
private bool $sticky = true;
|
private bool $sticky = true;
|
||||||
private ?string $paginationRoute = null;
|
private ?string $paginationRoute = null;
|
||||||
|
|
||||||
public function __construct(private string $tableName, private BaseQuery $query)
|
public function __construct(
|
||||||
|
private readonly string $tableName,
|
||||||
|
private readonly BaseQuery $query
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,17 +9,17 @@
|
|||||||
|
|
||||||
namespace App\Widget\Type;
|
namespace App\Widget\Type;
|
||||||
|
|
||||||
use App\Entity\Project;
|
|
||||||
use App\Entity\Team;
|
|
||||||
use App\Project\ProjectStatisticService;
|
use App\Project\ProjectStatisticService;
|
||||||
use App\Repository\Loader\ProjectLoader;
|
|
||||||
use App\Repository\Loader\TeamLoader;
|
use App\Repository\Loader\TeamLoader;
|
||||||
use App\Widget\WidgetInterface;
|
use App\Widget\WidgetInterface;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
final class UserTeamProjects extends AbstractWidget
|
final class UserTeamProjects extends AbstractWidget
|
||||||
{
|
{
|
||||||
public function __construct(private ProjectStatisticService $statisticService, private EntityManagerInterface $entityManager)
|
public function __construct(
|
||||||
|
private readonly ProjectStatisticService $statisticService,
|
||||||
|
private readonly EntityManagerInterface $entityManager
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,33 +65,22 @@ final class UserTeamProjects extends AbstractWidget
|
|||||||
public function getData(array $options = []): mixed
|
public function getData(array $options = []): mixed
|
||||||
{
|
{
|
||||||
$user = $this->getUser();
|
$user = $this->getUser();
|
||||||
|
$teams = $user->getTeams();
|
||||||
$now = new \DateTime('now', new \DateTimeZone($user->getTimezone()));
|
$now = new \DateTime('now', new \DateTimeZone($user->getTimezone()));
|
||||||
|
|
||||||
$loader = new TeamLoader($this->entityManager);
|
$loader = new TeamLoader($this->entityManager, true);
|
||||||
$loader->loadResults($user->getTeams());
|
$loader->loadResults($teams);
|
||||||
|
|
||||||
$teamProjects = [];
|
|
||||||
$projects = [];
|
$projects = [];
|
||||||
|
|
||||||
/** @var Team $team */
|
foreach ($teams as $team) {
|
||||||
foreach ($user->getTeams() as $team) {
|
|
||||||
/** @var Project $project */
|
|
||||||
foreach ($team->getProjects() as $project) {
|
foreach ($team->getProjects() as $project) {
|
||||||
if (!isset($teamProjects[$project->getId()])) {
|
|
||||||
$teamProjects[$project->getId()] = $project;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$loader = new ProjectLoader($this->entityManager, false, false, false);
|
|
||||||
$loader->loadResults($teamProjects);
|
|
||||||
|
|
||||||
foreach ($teamProjects as $id => $project) {
|
|
||||||
if (!$project->isVisibleAtDate($now) || !$project->hasBudgets()) {
|
if (!$project->isVisibleAtDate($now) || !$project->hasBudgets()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$projects[$project->getId()] = $project;
|
$projects[$project->getId()] = $project;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $this->statisticService->getBudgetStatisticModelForProjects($projects, $now);
|
return $this->statisticService->getBudgetStatisticModelForProjects($projects, $now);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,10 @@
|
|||||||
|
|
||||||
namespace App\Widget\Type;
|
namespace App\Widget\Type;
|
||||||
|
|
||||||
use App\Repository\Loader\UserLoader;
|
|
||||||
use App\Widget\WidgetInterface;
|
use App\Widget\WidgetInterface;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
final class UserTeams extends AbstractWidget
|
final class UserTeams extends AbstractWidget
|
||||||
{
|
{
|
||||||
public function __construct(private EntityManagerInterface $entityManager)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getWidth(): int
|
public function getWidth(): int
|
||||||
{
|
{
|
||||||
return WidgetInterface::WIDTH_HALF;
|
return WidgetInterface::WIDTH_HALF;
|
||||||
@@ -57,12 +51,6 @@ final class UserTeams extends AbstractWidget
|
|||||||
*/
|
*/
|
||||||
public function getData(array $options = []): mixed
|
public function getData(array $options = []): mixed
|
||||||
{
|
{
|
||||||
$user = $this->getUser();
|
return $this->getUser()->getTeams();
|
||||||
|
|
||||||
// without this, every user would be lazy loaded
|
|
||||||
$loader = new UserLoader($this->entityManager, true);
|
|
||||||
$loader->loadResults([$user->getId()]);
|
|
||||||
|
|
||||||
return $user->getTeams();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use App\Tests\DataFixtures\TimesheetFixtures;
|
|||||||
*/
|
*/
|
||||||
abstract class AbstractUserPeriodControllerTest extends ControllerBaseTest
|
abstract class AbstractUserPeriodControllerTest extends ControllerBaseTest
|
||||||
{
|
{
|
||||||
protected function importReportingFixture(string $role)
|
protected function importReportingFixture(string $role): void
|
||||||
{
|
{
|
||||||
$fixture = new TimesheetFixtures();
|
$fixture = new TimesheetFixtures();
|
||||||
$fixture->setAmount(50);
|
$fixture->setAmount(50);
|
||||||
|
|||||||
@@ -356,16 +356,10 @@ final class TimesheetFixtures implements TestFixture
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param User $user
|
|
||||||
* @param Activity $activity
|
|
||||||
* @param Project $project
|
|
||||||
* @param string $description
|
|
||||||
* @param \DateTime $start
|
* @param \DateTime $start
|
||||||
* @param null|array $tagArray
|
* @param array<Tag> $tagArray
|
||||||
* @param bool $setEndDate
|
|
||||||
* @return Timesheet
|
|
||||||
*/
|
*/
|
||||||
private function createTimesheetEntry(User $user, Activity $activity, Project $project, $description, \DateTime $start, $tagArray = [], $setEndDate = true)
|
private function createTimesheetEntry(User $user, Activity $activity, Project $project, ?string $description, \DateTime $start, array $tagArray = [], bool $setEndDate = true): Timesheet
|
||||||
{
|
{
|
||||||
$end = clone $start;
|
$end = clone $start;
|
||||||
$end = $end->modify('+ ' . (rand(1, 86400)) . ' seconds');
|
$end = $end->modify('+ ' . (rand(1, 86400)) . ' seconds');
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
namespace App\Tests\Repository\Loader;
|
namespace App\Tests\Repository\Loader;
|
||||||
|
|
||||||
use App\Entity\Activity;
|
use App\Entity\Activity;
|
||||||
|
use App\Entity\Project;
|
||||||
use App\Repository\Loader\ActivityLoader;
|
use App\Repository\Loader\ActivityLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,12 +20,15 @@ class ActivityLoaderTest extends AbstractLoaderTest
|
|||||||
{
|
{
|
||||||
public function testLoadResults(): void
|
public function testLoadResults(): void
|
||||||
{
|
{
|
||||||
$em = $this->getEntityManagerMock(3);
|
$project = $this->createMock(Project::class);
|
||||||
|
$project->expects($this->once())->method('getId')->willReturn(13);
|
||||||
$sut = new ActivityLoader($em);
|
|
||||||
|
|
||||||
$entity = $this->createMock(Activity::class);
|
$entity = $this->createMock(Activity::class);
|
||||||
$entity->expects($this->once())->method('getId')->willReturn(1);
|
$entity->expects($this->once())->method('getId')->willReturn(1);
|
||||||
|
$entity->expects($this->exactly(3))->method('getProject')->willReturn($project);
|
||||||
|
|
||||||
|
$em = $this->getEntityManagerMock(3);
|
||||||
|
$sut = new ActivityLoader($em);
|
||||||
|
|
||||||
$sut->loadResults([$entity]);
|
$sut->loadResults([$entity]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace App\Tests\Repository\Loader;
|
|||||||
|
|
||||||
use App\Entity\Customer;
|
use App\Entity\Customer;
|
||||||
use App\Repository\Loader\CustomerLoader;
|
use App\Repository\Loader\CustomerLoader;
|
||||||
|
use App\Repository\Query\CustomerQuery;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers \App\Repository\Loader\CustomerLoader
|
* @covers \App\Repository\Loader\CustomerLoader
|
||||||
@@ -19,13 +20,15 @@ class CustomerLoaderTest extends AbstractLoaderTest
|
|||||||
{
|
{
|
||||||
public function testLoadResults(): void
|
public function testLoadResults(): void
|
||||||
{
|
{
|
||||||
$em = $this->getEntityManagerMock(2);
|
$em = $this->getEntityManagerMock(1);
|
||||||
|
|
||||||
$sut = new CustomerLoader($em);
|
$query = new CustomerQuery();
|
||||||
|
$query->loadTeams();
|
||||||
|
|
||||||
|
$sut = new CustomerLoader($em, $query);
|
||||||
|
|
||||||
$entity = $this->createMock(Customer::class);
|
$entity = $this->createMock(Customer::class);
|
||||||
$entity->expects($this->once())->method('getId')->willReturn(1);
|
|
||||||
|
|
||||||
$sut->loadResults([$entity, 4711]);
|
$sut->loadResults([$entity]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Kimai time-tracking app.
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Tests\Repository\Loader;
|
|
||||||
|
|
||||||
use App\Repository\Loader\DefaultLoader;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @covers \App\Repository\Loader\DefaultLoader
|
|
||||||
*/
|
|
||||||
class DefaultLoaderTest extends TestCase
|
|
||||||
{
|
|
||||||
public function testLoadResults(): void
|
|
||||||
{
|
|
||||||
$sut = new DefaultLoader();
|
|
||||||
|
|
||||||
$input = [];
|
|
||||||
$sut->loadResults($input);
|
|
||||||
|
|
||||||
self::assertEquals([], $input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Kimai time-tracking app.
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Tests\Repository\Loader;
|
|
||||||
|
|
||||||
use App\Entity\Invoice;
|
|
||||||
use App\Repository\Loader\InvoiceLoader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @covers \App\Repository\Loader\InvoiceLoader
|
|
||||||
*/
|
|
||||||
class InvoiceLoaderTest extends AbstractLoaderTest
|
|
||||||
{
|
|
||||||
public function testLoadResults(): void
|
|
||||||
{
|
|
||||||
$em = $this->getEntityManagerMock(3);
|
|
||||||
|
|
||||||
$sut = new InvoiceLoader($em);
|
|
||||||
|
|
||||||
$entity = $this->createMock(Invoice::class);
|
|
||||||
$entity->expects($this->once())->method('getId')->willReturn(1);
|
|
||||||
|
|
||||||
$sut->loadResults([$entity]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,13 +25,13 @@ class ProjectLoaderTest extends AbstractLoaderTest
|
|||||||
|
|
||||||
$entity = $this->createMock(Project::class);
|
$entity = $this->createMock(Project::class);
|
||||||
$entity->expects($this->once())->method('getId')->willReturn(1);
|
$entity->expects($this->once())->method('getId')->willReturn(1);
|
||||||
$entity->expects($this->once())->method('getCustomer')->willReturn($customer);
|
$entity->expects($this->exactly(2))->method('getCustomer')->willReturn($customer);
|
||||||
|
|
||||||
$results = [$entity];
|
$results = [$entity];
|
||||||
|
|
||||||
$em = $this->getEntityManagerMock(4, $results);
|
$em = $this->getEntityManagerMock(2, $results);
|
||||||
|
|
||||||
$sut = new ProjectLoader($em);
|
$sut = new ProjectLoader($em);
|
||||||
$sut->loadResults([$entity, 4711]);
|
$sut->loadResults([$entity]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of the Kimai time-tracking app.
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace App\Tests\Repository\Paginator;
|
|
||||||
|
|
||||||
use App\Repository\Paginator\QueryBuilderPaginator;
|
|
||||||
use Doctrine\ORM\EntityManager;
|
|
||||||
use Doctrine\ORM\QueryBuilder;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @covers \App\Repository\Paginator\QueryBuilderPaginator
|
|
||||||
*/
|
|
||||||
class QueryBuilderPaginatorTest extends TestCase
|
|
||||||
{
|
|
||||||
public function testPaginator(): void
|
|
||||||
{
|
|
||||||
$em = $this->createMock(EntityManager::class);
|
|
||||||
$qb = new QueryBuilder($em);
|
|
||||||
$sut = new QueryBuilderPaginator($qb, 10);
|
|
||||||
|
|
||||||
self::assertEquals(10, $sut->getNbResults());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -18,7 +18,6 @@ use App\Form\Model\DateRange;
|
|||||||
use App\Repository\Query\ActivityQuery;
|
use App\Repository\Query\ActivityQuery;
|
||||||
use App\Repository\Query\BaseQuery;
|
use App\Repository\Query\BaseQuery;
|
||||||
use App\Repository\Query\DateRangeInterface;
|
use App\Repository\Query\DateRangeInterface;
|
||||||
use App\Repository\Query\ProjectQuery;
|
|
||||||
use App\Repository\Query\TimesheetQuery;
|
use App\Repository\Query\TimesheetQuery;
|
||||||
use App\Utils\SearchTerm;
|
use App\Utils\SearchTerm;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
@@ -251,8 +250,14 @@ class BaseQueryTest extends TestCase
|
|||||||
$this->assertEquals([13, 27], $sut->getActivityIds());
|
$this->assertEquals([13, 27], $sut->getActivityIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function assertCustomer(ProjectQuery $sut): void
|
protected function assertCustomer(BaseQuery $sut): void
|
||||||
{
|
{
|
||||||
|
$this->assertTrue(method_exists($sut, 'getCustomers'));
|
||||||
|
$this->assertTrue(method_exists($sut, 'setCustomers'));
|
||||||
|
$this->assertTrue(method_exists($sut, 'hasCustomers'));
|
||||||
|
$this->assertTrue(method_exists($sut, 'addCustomer'));
|
||||||
|
$this->assertTrue(method_exists($sut, 'getCustomerIds'));
|
||||||
|
|
||||||
$this->assertEquals([], $sut->getCustomers());
|
$this->assertEquals([], $sut->getCustomers());
|
||||||
$this->assertFalse($sut->hasCustomers());
|
$this->assertFalse($sut->hasCustomers());
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class AmountYearTest extends AbstractWidgetTypeTest
|
|||||||
public function createSut(): AbstractWidgetType
|
public function createSut(): AbstractWidgetType
|
||||||
{
|
{
|
||||||
$repository = $this->createMock(TimesheetRepository::class);
|
$repository = $this->createMock(TimesheetRepository::class);
|
||||||
$repository->method('getStatistic')->willReturn([]);
|
|
||||||
$configuration = SystemConfigurationFactory::createStub();
|
$configuration = SystemConfigurationFactory::createStub();
|
||||||
$dispatcher = $this->createMock(EventDispatcherInterface::class);
|
$dispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class UserAmountYearTest extends AbstractWidgetTypeTest
|
|||||||
public function createSut(): AbstractWidgetType
|
public function createSut(): AbstractWidgetType
|
||||||
{
|
{
|
||||||
$repository = $this->createMock(TimesheetRepository::class);
|
$repository = $this->createMock(TimesheetRepository::class);
|
||||||
$repository->method('getStatistic')->willReturn([]);
|
|
||||||
$configuration = SystemConfigurationFactory::createStub();
|
$configuration = SystemConfigurationFactory::createStub();
|
||||||
$dispatcher = $this->createMock(EventDispatcherInterface::class);
|
$dispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||||
|
|
||||||
|
|||||||
@@ -1218,11 +1218,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: Controller/Reporting/AbstractUserPeriodControllerTest.php
|
path: Controller/Reporting/AbstractUserPeriodControllerTest.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\Controller\\\\Reporting\\\\AbstractUserPeriodControllerTest\\:\\:importReportingFixture\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: Controller/Reporting/AbstractUserPeriodControllerTest.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, string\\|false given\\.$#"
|
message: "#^Parameter \\#2 \\$haystack of static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertStringContainsString\\(\\) expects string, string\\|false given\\.$#"
|
||||||
count: 2
|
count: 2
|
||||||
@@ -1468,21 +1463,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: DataFixtures/TestFixture.php
|
path: DataFixtures/TestFixture.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Tests\\\\DataFixtures\\\\TimesheetFixtures\\:\\:createTimesheetEntry\\(\\) has parameter \\$tagArray with no value type specified in iterable type array\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: DataFixtures/TimesheetFixtures.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$value of function count expects array\\|Countable, array\\|null given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: DataFixtures/TimesheetFixtures.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#4 \\$description of method App\\\\Tests\\\\DataFixtures\\\\TimesheetFixtures\\:\\:createTimesheetEntry\\(\\) expects string, string\\|null given\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: DataFixtures/TimesheetFixtures.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot access offset 'foo\\-bundle\\.bar' on array\\|bool\\|float\\|int\\|string\\|null\\.$#"
|
message: "#^Cannot access offset 'foo\\-bundle\\.bar' on array\\|bool\\|float\\|int\\|string\\|null\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -1598,11 +1578,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: Entity/ProjectTest.php
|
path: Entity/ProjectTest.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot access offset 0 on iterable\\<App\\\\Entity\\\\Team\\>\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: Entity/TeamTest.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot call method getUser\\(\\) on mixed\\.$#"
|
message: "#^Cannot call method getUser\\(\\) on mixed\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
@@ -1633,11 +1608,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: Entity/TimesheetValidationTest.php
|
path: Entity/TimesheetValidationTest.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot access offset 0 on iterable\\<App\\\\Entity\\\\Team\\>\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: Entity/UserTest.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#1 \\$string of function mb_strlen expects string, string\\|null given\\.$#"
|
message: "#^Parameter \\#1 \\$string of function mb_strlen expects string, string\\|null given\\.$#"
|
||||||
count: 5
|
count: 5
|
||||||
|
|||||||
Reference in New Issue
Block a user