mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-29 21:14:29 +02:00
Merge branch 'master' into log_system
This commit is contained in:
commit
3f8cd6473a
9 changed files with 1167 additions and 684 deletions
|
@ -70,9 +70,12 @@ for additional informations.
|
|||
* Change the line `APP_ENV=dev` to `APP_ENV=prod`
|
||||
* Change the value of `DATABASE_URL=` to your needs (see [here](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url)) for the format.
|
||||
4. Install composer dependencies and generate autoload files: `composer install --no-dev`
|
||||
5. Install client side dependencies and build it: `yarn install` and `yarn build`
|
||||
6. Optional (speeds up first load): Warmup cache: `php bin/console cache:warmup`
|
||||
7. Upgrade database to new scheme (or create it, when it was empty): `php bin/console doctrine:migrations:migrate` and follow the instructions given. **Caution**: This steps tamper with your database and could potentially destroy it. So make sure to make a backup of your database.
|
||||
5. If you have put Part-DB into a sub-directory on your server (like `part-db/`), you have to edit the file
|
||||
`webpack.config.js` and uncomment the lines (remove the `//` before the lines) `.setPublicPath('/part-db/build')` (line 43) and
|
||||
`.setManifestKeyPrefix('build/')` (line 44). You have to replace `/part-db` with your own path on line 44.
|
||||
6. Install client side dependencies and build it: `yarn install` and `yarn build`
|
||||
7. _Optional_ (speeds up first load): Warmup cache: `php bin/console cache:warmup`
|
||||
8. Upgrade database to new scheme (or create it, when it was empty): `php bin/console doctrine:migrations:migrate` and follow the instructions given. **Caution**: This steps tamper with your database and could potentially destroy it. So make sure to make a backup of your database.
|
||||
|
||||
When you want to upgrade to a newer version, then just copy the new files into the folder
|
||||
and repeat the steps 4. to 7.
|
||||
|
|
106
composer.lock
generated
106
composer.lock
generated
|
@ -465,16 +465,16 @@
|
|||
},
|
||||
{
|
||||
"name": "doctrine/doctrine-bundle",
|
||||
"version": "2.0.6",
|
||||
"version": "2.0.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/DoctrineBundle.git",
|
||||
"reference": "0ef972d3b730f975c80db9fffa4b2a0258c91442"
|
||||
"reference": "6926771140ee87a823c3b2c72602de9dda4490d3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/0ef972d3b730f975c80db9fffa4b2a0258c91442",
|
||||
"reference": "0ef972d3b730f975c80db9fffa4b2a0258c91442",
|
||||
"url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/6926771140ee87a823c3b2c72602de9dda4490d3",
|
||||
"reference": "6926771140ee87a823c3b2c72602de9dda4490d3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -501,6 +501,7 @@
|
|||
"phpunit/phpunit": "^7.5",
|
||||
"symfony/phpunit-bridge": "^4.2",
|
||||
"symfony/property-info": "^4.3.3|^5.0",
|
||||
"symfony/proxy-manager-bridge": "^3.4|^4.3.3|^5.0",
|
||||
"symfony/twig-bridge": "^3.4.30|^4.3.3|^5.0",
|
||||
"symfony/validator": "^3.4.30|^4.3.3|^5.0",
|
||||
"symfony/web-profiler-bundle": "^3.4.30|^4.3.3|^5.0",
|
||||
|
@ -552,7 +553,7 @@
|
|||
"orm",
|
||||
"persistence"
|
||||
],
|
||||
"time": "2019-12-19T13:47:07+00:00"
|
||||
"time": "2020-01-18T11:56:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/doctrine-migrations-bundle",
|
||||
|
@ -1050,16 +1051,16 @@
|
|||
},
|
||||
{
|
||||
"name": "doctrine/persistence",
|
||||
"version": "1.3.4",
|
||||
"version": "1.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/persistence.git",
|
||||
"reference": "ff7e08b0f814be2cd20c52dc3c8a262579376b94"
|
||||
"reference": "5dd3ac5eebef2d0b074daa4440bb18f93132dee4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/persistence/zipball/ff7e08b0f814be2cd20c52dc3c8a262579376b94",
|
||||
"reference": "ff7e08b0f814be2cd20c52dc3c8a262579376b94",
|
||||
"url": "https://api.github.com/repos/doctrine/persistence/zipball/5dd3ac5eebef2d0b074daa4440bb18f93132dee4",
|
||||
"reference": "5dd3ac5eebef2d0b074daa4440bb18f93132dee4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1067,7 +1068,7 @@
|
|||
"doctrine/cache": "^1.0",
|
||||
"doctrine/collections": "^1.0",
|
||||
"doctrine/event-manager": "^1.0",
|
||||
"doctrine/reflection": "^1.0",
|
||||
"doctrine/reflection": "^1.1",
|
||||
"php": "^7.1"
|
||||
},
|
||||
"conflict": {
|
||||
|
@ -1129,7 +1130,7 @@
|
|||
"orm",
|
||||
"persistence"
|
||||
],
|
||||
"time": "2020-01-09T19:49:17+00:00"
|
||||
"time": "2020-01-16T22:06:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/reflection",
|
||||
|
@ -1520,16 +1521,16 @@
|
|||
},
|
||||
{
|
||||
"name": "gregwar/captcha-bundle",
|
||||
"version": "v2.1.0",
|
||||
"version": "v2.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Gregwar/CaptchaBundle.git",
|
||||
"reference": "a22ba77f5237f878d0b97cbe4310c42c29c74112"
|
||||
"reference": "d4475118d080e764c5cddec766b0cd0b12813bef"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Gregwar/CaptchaBundle/zipball/a22ba77f5237f878d0b97cbe4310c42c29c74112",
|
||||
"reference": "a22ba77f5237f878d0b97cbe4310c42c29c74112",
|
||||
"url": "https://api.github.com/repos/Gregwar/CaptchaBundle/zipball/d4475118d080e764c5cddec766b0cd0b12813bef",
|
||||
"reference": "d4475118d080e764c5cddec766b0cd0b12813bef",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1577,7 +1578,7 @@
|
|||
"symfony",
|
||||
"visual"
|
||||
],
|
||||
"time": "2020-01-08T15:31:01+00:00"
|
||||
"time": "2020-01-14T10:15:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "imagine/imagine",
|
||||
|
@ -2739,16 +2740,16 @@
|
|||
},
|
||||
{
|
||||
"name": "php-translation/common",
|
||||
"version": "3.0.0",
|
||||
"version": "3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-translation/common.git",
|
||||
"reference": "dbf73744a22121b85e369ac73ae658c80dd888b7"
|
||||
"reference": "b786f49d7f222ca345b0e4c32d4a205a845b0e27"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-translation/common/zipball/dbf73744a22121b85e369ac73ae658c80dd888b7",
|
||||
"reference": "dbf73744a22121b85e369ac73ae658c80dd888b7",
|
||||
"url": "https://api.github.com/repos/php-translation/common/zipball/b786f49d7f222ca345b0e4c32d4a205a845b0e27",
|
||||
"reference": "b786f49d7f222ca345b0e4c32d4a205a845b0e27",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2776,7 +2777,7 @@
|
|||
}
|
||||
],
|
||||
"description": "Common translation stuff",
|
||||
"time": "2019-12-30T10:58:18+00:00"
|
||||
"time": "2020-01-18T09:48:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "php-translation/extractor",
|
||||
|
@ -3668,16 +3669,16 @@
|
|||
},
|
||||
{
|
||||
"name": "scheb/two-factor-bundle",
|
||||
"version": "v4.11.1",
|
||||
"version": "v4.12.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/scheb/two-factor-bundle.git",
|
||||
"reference": "f9198cfcd5b2a92691926fd10406e8817232ac16"
|
||||
"reference": "d45bac6a6e4932ac43c2a478a85bf9ab5cb9efb4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/scheb/two-factor-bundle/zipball/f9198cfcd5b2a92691926fd10406e8817232ac16",
|
||||
"reference": "f9198cfcd5b2a92691926fd10406e8817232ac16",
|
||||
"url": "https://api.github.com/repos/scheb/two-factor-bundle/zipball/d45bac6a6e4932ac43c2a478a85bf9ab5cb9efb4",
|
||||
"reference": "d45bac6a6e4932ac43c2a478a85bf9ab5cb9efb4",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3732,7 +3733,7 @@
|
|||
"two-factor",
|
||||
"two-step"
|
||||
],
|
||||
"time": "2019-12-19T12:07:42+00:00"
|
||||
"time": "2020-01-12T14:01:09+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sensio/framework-extra-bundle",
|
||||
|
@ -8771,24 +8772,23 @@
|
|||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "1.5.0",
|
||||
"version": "1.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/semver.git",
|
||||
"reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e"
|
||||
"reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e",
|
||||
"reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
|
||||
"reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.3.2 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.5 || ^5.0.5",
|
||||
"phpunit/phpunit-mock-objects": "2.3.0 || ^3.0"
|
||||
"phpunit/phpunit": "^4.5 || ^5.0.5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
|
@ -8829,7 +8829,7 @@
|
|||
"validation",
|
||||
"versioning"
|
||||
],
|
||||
"time": "2019-03-19T17:25:45+00:00"
|
||||
"time": "2020-01-13T12:06:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/xdebug-handler",
|
||||
|
@ -8934,20 +8934,21 @@
|
|||
},
|
||||
{
|
||||
"name": "doctrine/data-fixtures",
|
||||
"version": "1.4.0",
|
||||
"version": "1.4.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/data-fixtures.git",
|
||||
"reference": "608a35a3b5bcc4214d116603095f8b0c51091592"
|
||||
"reference": "39e9777c9089351a468f780b01cffa3cb0a42907"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/608a35a3b5bcc4214d116603095f8b0c51091592",
|
||||
"reference": "608a35a3b5bcc4214d116603095f8b0c51091592",
|
||||
"url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/39e9777c9089351a468f780b01cffa3cb0a42907",
|
||||
"reference": "39e9777c9089351a468f780b01cffa3cb0a42907",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/common": "^2.11",
|
||||
"doctrine/persistence": "^1.3.3",
|
||||
"php": "^7.2"
|
||||
},
|
||||
"conflict": {
|
||||
|
@ -8958,7 +8959,7 @@
|
|||
"doctrine/coding-standard": "^6.0",
|
||||
"doctrine/dbal": "^2.5.4",
|
||||
"doctrine/mongodb-odm": "^1.3.0",
|
||||
"doctrine/orm": "^2.5.4",
|
||||
"doctrine/orm": "^2.7.0",
|
||||
"phpunit/phpunit": "^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
|
@ -8993,7 +8994,7 @@
|
|||
"keywords": [
|
||||
"database"
|
||||
],
|
||||
"time": "2019-10-30T20:03:18+00:00"
|
||||
"time": "2020-01-17T11:11:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/doctrine-fixtures-bundle",
|
||||
|
@ -10294,16 +10295,16 @@
|
|||
},
|
||||
{
|
||||
"name": "slevomat/coding-standard",
|
||||
"version": "6.0.6",
|
||||
"version": "6.0.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/slevomat/coding-standard.git",
|
||||
"reference": "3cc9f2fd62bf68447a9979a9d11cdba94e35c530"
|
||||
"reference": "dd399a5ba9b112cb2d0ac1541875ee64d0cbf4a1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/slevomat/coding-standard/zipball/3cc9f2fd62bf68447a9979a9d11cdba94e35c530",
|
||||
"reference": "3cc9f2fd62bf68447a9979a9d11cdba94e35c530",
|
||||
"url": "https://api.github.com/repos/slevomat/coding-standard/zipball/dd399a5ba9b112cb2d0ac1541875ee64d0cbf4a1",
|
||||
"reference": "dd399a5ba9b112cb2d0ac1541875ee64d0cbf4a1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -10331,7 +10332,7 @@
|
|||
"MIT"
|
||||
],
|
||||
"description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.",
|
||||
"time": "2020-01-09T19:52:44+00:00"
|
||||
"time": "2020-01-14T08:39:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "squizlabs/php_codesniffer",
|
||||
|
@ -11189,16 +11190,16 @@
|
|||
},
|
||||
{
|
||||
"name": "vimeo/psalm",
|
||||
"version": "3.8.2",
|
||||
"version": "3.8.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/vimeo/psalm.git",
|
||||
"reference": "90d6b73fd8062432030ef39b7b6694b3902daa31"
|
||||
"reference": "389af1bfc739bfdff3f9e3dc7bd6499aee51a831"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/vimeo/psalm/zipball/90d6b73fd8062432030ef39b7b6694b3902daa31",
|
||||
"reference": "90d6b73fd8062432030ef39b7b6694b3902daa31",
|
||||
"url": "https://api.github.com/repos/vimeo/psalm/zipball/389af1bfc739bfdff3f9e3dc7bd6499aee51a831",
|
||||
"reference": "389af1bfc739bfdff3f9e3dc7bd6499aee51a831",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -11218,7 +11219,7 @@
|
|||
"openlss/lib-array2xml": "^1.0",
|
||||
"php": "^7.1.3",
|
||||
"sebastian/diff": "^3.0",
|
||||
"symfony/console": "^3.3||^4.0||^5.0",
|
||||
"symfony/console": "^3.4.17 || ^4.1.6 || ^5.0",
|
||||
"webmozart/glob": "^4.1",
|
||||
"webmozart/path-util": "^2.3"
|
||||
},
|
||||
|
@ -11228,7 +11229,6 @@
|
|||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.2",
|
||||
"ext-curl": "*",
|
||||
"friendsofphp/php-cs-fixer": "^2.15",
|
||||
"phpmyadmin/sql-parser": "^5.0",
|
||||
"phpspec/prophecy": ">=1.9.0",
|
||||
"phpunit/phpunit": "^7.5 || ^8.0",
|
||||
|
@ -11242,10 +11242,10 @@
|
|||
},
|
||||
"bin": [
|
||||
"psalm",
|
||||
"psalter",
|
||||
"psalm-language-server",
|
||||
"psalm-plugin",
|
||||
"psalm-refactor"
|
||||
"psalm-refactor",
|
||||
"psalter"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
|
@ -11280,7 +11280,7 @@
|
|||
"inspection",
|
||||
"php"
|
||||
],
|
||||
"time": "2020-01-07T13:50:31+00:00"
|
||||
"time": "2020-01-15T03:46:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "webmozart/glob",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"@fortawesome/fontawesome-free": "^5.7.2",
|
||||
"@symfony/webpack-encore": "^0.28.2",
|
||||
"bootstrap": "^4.4.1",
|
||||
"core-js": "^3.0.0",
|
||||
"core-js": "^3.6.4",
|
||||
"jquery": "^3.3.1",
|
||||
"popper.js": "^1.14.7",
|
||||
"regenerator-runtime": "^0.13.2",
|
||||
|
@ -48,7 +48,7 @@
|
|||
"pdfmake": "^0.1.53",
|
||||
"qrcode": "^1.4.4",
|
||||
"ts-loader": "^5.3.0",
|
||||
"typescript": "^3.3.4000",
|
||||
"typescript": "^3.7.5",
|
||||
"u2f-api": "^1.1.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
namespace App\DataTables\Adapter;
|
||||
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Omines\DataTablesBundle\Adapter\AdapterQuery;
|
||||
use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapter;
|
||||
use Omines\DataTablesBundle\Column\AbstractColumn;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Override default ORM Adapter, to allow fetch joins (allow addSelect with ManyToOne Collections).
|
||||
* This should improves performance for Part Tables.
|
||||
* Based on: https://github.com/omines/datatables-bundle/blob/master/tests/Fixtures/AppBundle/DataTable/Adapter/CustomORMAdapter.php.
|
||||
*/
|
||||
class CustomORMAdapter extends ORMAdapter
|
||||
{
|
||||
protected $hydrationMode;
|
||||
|
||||
public function configure(array $options): void
|
||||
{
|
||||
parent::configure($options);
|
||||
$this->hydrationMode = $options['hydrate'] ?? Query::HYDRATE_OBJECT;
|
||||
}
|
||||
|
||||
protected function prepareQuery(AdapterQuery $query): void
|
||||
{
|
||||
parent::prepareQuery($query);
|
||||
$query->setIdentifierPropertyPath(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Traversable
|
||||
*/
|
||||
protected function getResults(AdapterQuery $query): Traversable
|
||||
{
|
||||
/** @var QueryBuilder $builder */
|
||||
$builder = $query->get('qb');
|
||||
$state = $query->getState();
|
||||
// Apply definitive view state for current 'page' of the table
|
||||
foreach ($state->getOrderBy() as [$column, $direction]) {
|
||||
/** @var AbstractColumn $column */
|
||||
if ($column->isOrderable()) {
|
||||
$builder->addOrderBy($column->getOrderField(), $direction);
|
||||
}
|
||||
}
|
||||
if ($state->getLength() > 0) {
|
||||
$builder
|
||||
->setFirstResult($state->getStart())
|
||||
->setMaxResults($state->getLength());
|
||||
}
|
||||
/*
|
||||
* Use foreach instead of iterate to prevent group by from crashing
|
||||
*/
|
||||
foreach ($builder->getQuery()->getResult($this->hydrationMode) as $result) {
|
||||
/*
|
||||
* Return everything instead of first element
|
||||
*/
|
||||
yield $result;
|
||||
}
|
||||
}
|
||||
}
|
145
src/DataTables/Adapter/FetchJoinORMAdapter.php
Normal file
145
src/DataTables/Adapter/FetchJoinORMAdapter.php
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
/**
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
namespace App\DataTables\Adapter;
|
||||
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
use Omines\DataTablesBundle\Adapter\AdapterQuery;
|
||||
use Omines\DataTablesBundle\Adapter\Doctrine\Event\ORMAdapterQueryEvent;
|
||||
use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapterEvents;
|
||||
use Omines\DataTablesBundle\Column\AbstractColumn;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Similar to ORMAdapter this class allows to access objects from the doctrine ORM.
|
||||
* Unlike the default ORMAdapter supports Fetch Joins (additional entites are fetched from DB via joins) using
|
||||
* the Doctrine Paginator.
|
||||
* @author Jan Böhmer
|
||||
*/
|
||||
class FetchJoinORMAdapter extends ORMAdapter
|
||||
{
|
||||
protected $use_simple_total;
|
||||
|
||||
public function configure(array $options)
|
||||
{
|
||||
parent::configure($options);
|
||||
$this->use_simple_total = $options['simple_total_query'];
|
||||
}
|
||||
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
parent::configureOptions($resolver);
|
||||
|
||||
//Enforce object hydration mode (fetch join only works for objects)
|
||||
$resolver->addAllowedValues('hydrate', Query::HYDRATE_OBJECT);
|
||||
|
||||
/**
|
||||
* Add the possibility to replace the query for total entity count through a very simple one, to improve performance.
|
||||
* You can only use this option, if you did not apply any criteria to your total count.
|
||||
*/
|
||||
$resolver->setDefault('simple_total_query', false);
|
||||
|
||||
return $resolver;
|
||||
}
|
||||
|
||||
protected function prepareQuery(AdapterQuery $query)
|
||||
{
|
||||
$state = $query->getState();
|
||||
$query->set('qb', $builder = $this->createQueryBuilder($state));
|
||||
$query->set('rootAlias', $rootAlias = $builder->getDQLPart('from')[0]->getAlias());
|
||||
|
||||
// Provide default field mappings if needed
|
||||
foreach ($state->getDataTable()->getColumns() as $column) {
|
||||
if (null === $column->getField() && isset($this->metadata->fieldMappings[$name = $column->getName()])) {
|
||||
$column->setOption('field', "{$rootAlias}.{$name}");
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Query\Expr\From $fromClause */
|
||||
$fromClause = $builder->getDQLPart('from')[0];
|
||||
$identifier = "{$fromClause->getAlias()}.{$this->metadata->getSingleIdentifierFieldName()}";
|
||||
|
||||
//Use simpler (faster) total count query if the user wanted so...
|
||||
if ($this->use_simple_total) {
|
||||
$query->setTotalRows($this->getSimpleTotalCount($builder));
|
||||
} else {
|
||||
$query->setTotalRows($this->getCount($builder, $identifier));
|
||||
}
|
||||
|
||||
// Get record count after filtering
|
||||
$this->buildCriteria($builder, $state);
|
||||
$query->setFilteredRows($this->getCount($builder, $identifier));
|
||||
|
||||
// Perform mapping of all referred fields and implied fields
|
||||
$aliases = $this->getAliases($query);
|
||||
$query->set('aliases', $aliases);
|
||||
$query->setIdentifierPropertyPath($this->mapFieldToPropertyPath($identifier, $aliases));
|
||||
}
|
||||
|
||||
public function getResults(AdapterQuery $query): \Traversable
|
||||
{
|
||||
$builder = $query->get('qb');
|
||||
$state = $query->getState();
|
||||
|
||||
// Apply definitive view state for current 'page' of the table
|
||||
foreach ($state->getOrderBy() as list($column, $direction)) {
|
||||
/** @var AbstractColumn $column */
|
||||
if ($column->isOrderable()) {
|
||||
$builder->addOrderBy($column->getOrderField(), $direction);
|
||||
}
|
||||
}
|
||||
if ($state->getLength() > 0) {
|
||||
$builder
|
||||
->setFirstResult($state->getStart())
|
||||
->setMaxResults($state->getLength());
|
||||
}
|
||||
|
||||
$query = $builder->getQuery();
|
||||
$event = new ORMAdapterQueryEvent($query);
|
||||
$state->getDataTable()->getEventDispatcher()->dispatch($event, ORMAdapterEvents::PRE_QUERY);
|
||||
|
||||
//Use Doctrine paginator for result iteration
|
||||
$paginator = new Paginator($query);
|
||||
|
||||
foreach ($paginator->getIterator() as $result) {
|
||||
yield $result;
|
||||
$this->manager->detach($result);
|
||||
}
|
||||
}
|
||||
|
||||
public function getCount(QueryBuilder $queryBuilder, $identifier)
|
||||
{
|
||||
$paginator = new Paginator($queryBuilder);
|
||||
return $paginator->count();
|
||||
}
|
||||
|
||||
protected function getSimpleTotalCount(QueryBuilder $queryBuilder)
|
||||
{
|
||||
/** The paginator count queries can be rather slow, so when query for total count (100ms or longer),
|
||||
* just return the entity count.
|
||||
*/
|
||||
/** @var Query\Expr\From $from_expr */
|
||||
$from_expr = $queryBuilder->getDQLPart('from')[0];
|
||||
return $this->manager->getRepository($from_expr->getFrom())->count([]);
|
||||
}
|
||||
}
|
339
src/DataTables/Adapter/ORMAdapter.php
Normal file
339
src/DataTables/Adapter/ORMAdapter.php
Normal file
|
@ -0,0 +1,339 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* Symfony DataTables Bundle
|
||||
* (c) Omines Internetbureau B.V. - https://omines.nl/
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\DataTables\Adapter;
|
||||
|
||||
use Doctrine\Common\Persistence\ManagerRegistry;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Omines\DataTablesBundle\Adapter\AbstractAdapter;
|
||||
use Omines\DataTablesBundle\Adapter\AdapterQuery;
|
||||
use Omines\DataTablesBundle\Adapter\Doctrine\Event\ORMAdapterQueryEvent;
|
||||
use Omines\DataTablesBundle\Adapter\Doctrine\ORM\AutomaticQueryBuilder;
|
||||
use Omines\DataTablesBundle\Adapter\Doctrine\ORM\QueryBuilderProcessorInterface;
|
||||
use Omines\DataTablesBundle\Adapter\Doctrine\ORM\SearchCriteriaProvider;
|
||||
use Omines\DataTablesBundle\Column\AbstractColumn;
|
||||
use Omines\DataTablesBundle\DataTableState;
|
||||
use Omines\DataTablesBundle\Exception\InvalidConfigurationException;
|
||||
use Omines\DataTablesBundle\Exception\MissingDependencyException;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* ORMAdapter.
|
||||
*
|
||||
* @author Niels Keurentjes <niels.keurentjes@omines.com>
|
||||
* @author Robbert Beesems <robbert.beesems@omines.com>
|
||||
*/
|
||||
class ORMAdapter extends AbstractAdapter
|
||||
{
|
||||
/** @var ManagerRegistry */
|
||||
private $registry;
|
||||
|
||||
/** @var EntityManager */
|
||||
protected $manager;
|
||||
|
||||
/** @var \Doctrine\ORM\Mapping\ClassMetadata */
|
||||
protected $metadata;
|
||||
|
||||
/** @var int */
|
||||
private $hydrationMode;
|
||||
|
||||
/** @var QueryBuilderProcessorInterface[] */
|
||||
private $queryBuilderProcessors;
|
||||
|
||||
/** @var QueryBuilderProcessorInterface[] */
|
||||
protected $criteriaProcessors;
|
||||
|
||||
/**
|
||||
* DoctrineAdapter constructor.
|
||||
*/
|
||||
public function __construct(ManagerRegistry $registry = null)
|
||||
{
|
||||
if (null === $registry) {
|
||||
throw new MissingDependencyException('Install doctrine/doctrine-bundle to use the ORMAdapter');
|
||||
}
|
||||
|
||||
parent::__construct();
|
||||
$this->registry = $registry;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function configure(array $options)
|
||||
{
|
||||
$resolver = new OptionsResolver();
|
||||
$this->configureOptions($resolver);
|
||||
$options = $resolver->resolve($options);
|
||||
|
||||
// Enable automated mode or just get the general default entity manager
|
||||
if (null === ($this->manager = $this->registry->getManagerForClass($options['entity']))) {
|
||||
throw new InvalidConfigurationException(sprintf('Doctrine has no manager for entity "%s", is it correctly imported and referenced?', $options['entity']));
|
||||
}
|
||||
$this->metadata = $this->manager->getClassMetadata($options['entity']);
|
||||
if (empty($options['query'])) {
|
||||
$options['query'] = [new AutomaticQueryBuilder($this->manager, $this->metadata)];
|
||||
}
|
||||
|
||||
// Set options
|
||||
$this->hydrationMode = $options['hydrate'];
|
||||
$this->queryBuilderProcessors = $options['query'];
|
||||
$this->criteriaProcessors = $options['criteria'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $processor
|
||||
*/
|
||||
public function addCriteriaProcessor($processor)
|
||||
{
|
||||
$this->criteriaProcessors[] = $this->normalizeProcessor($processor);
|
||||
}
|
||||
|
||||
protected function prepareQuery(AdapterQuery $query)
|
||||
{
|
||||
$state = $query->getState();
|
||||
$query->set('qb', $builder = $this->createQueryBuilder($state));
|
||||
$query->set('rootAlias', $rootAlias = $builder->getDQLPart('from')[0]->getAlias());
|
||||
|
||||
// Provide default field mappings if needed
|
||||
foreach ($state->getDataTable()->getColumns() as $column) {
|
||||
if (null === $column->getField() && isset($this->metadata->fieldMappings[$name = $column->getName()])) {
|
||||
$column->setOption('field', "{$rootAlias}.{$name}");
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Query\Expr\From $fromClause */
|
||||
$fromClause = $builder->getDQLPart('from')[0];
|
||||
$identifier = "{$fromClause->getAlias()}.{$this->metadata->getSingleIdentifierFieldName()}";
|
||||
$query->setTotalRows($this->getCount($builder, $identifier));
|
||||
|
||||
// Get record count after filtering
|
||||
$this->buildCriteria($builder, $state);
|
||||
$query->setFilteredRows($this->getCount($builder, $identifier));
|
||||
|
||||
// Perform mapping of all referred fields and implied fields
|
||||
$aliases = $this->getAliases($query);
|
||||
$query->set('aliases', $aliases);
|
||||
$query->setIdentifierPropertyPath($this->mapFieldToPropertyPath($identifier, $aliases));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
protected function getAliases(AdapterQuery $query)
|
||||
{
|
||||
/** @var QueryBuilder $builder */
|
||||
$builder = $query->get('qb');
|
||||
$aliases = [];
|
||||
|
||||
/** @var Query\Expr\From $from */
|
||||
foreach ($builder->getDQLPart('from') as $from) {
|
||||
$aliases[$from->getAlias()] = [null, $this->manager->getMetadataFactory()->getMetadataFor($from->getFrom())];
|
||||
}
|
||||
|
||||
// Alias all joins
|
||||
foreach ($builder->getDQLPart('join') as $joins) {
|
||||
/** @var Query\Expr\Join $join */
|
||||
foreach ($joins as $join) {
|
||||
if (false === mb_strstr($join->getJoin(), '.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list($origin, $target) = explode('.', $join->getJoin());
|
||||
|
||||
$mapping = $aliases[$origin][1]->getAssociationMapping($target);
|
||||
$aliases[$join->getAlias()] = [$join->getJoin(), $this->manager->getMetadataFactory()->getMetadataFor($mapping['targetEntity'])];
|
||||
}
|
||||
}
|
||||
|
||||
return $aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function mapPropertyPath(AdapterQuery $query, AbstractColumn $column)
|
||||
{
|
||||
return $this->mapFieldToPropertyPath($column->getField(), $query->get('aliases'));
|
||||
}
|
||||
|
||||
protected function getResults(AdapterQuery $query): \Traversable
|
||||
{
|
||||
/** @var QueryBuilder $builder */
|
||||
$builder = $query->get('qb');
|
||||
$state = $query->getState();
|
||||
|
||||
// Apply definitive view state for current 'page' of the table
|
||||
foreach ($state->getOrderBy() as list($column, $direction)) {
|
||||
/** @var AbstractColumn $column */
|
||||
if ($column->isOrderable()) {
|
||||
$builder->addOrderBy($column->getOrderField(), $direction);
|
||||
}
|
||||
}
|
||||
if ($state->getLength() > 0) {
|
||||
$builder
|
||||
->setFirstResult($state->getStart())
|
||||
->setMaxResults($state->getLength())
|
||||
;
|
||||
}
|
||||
|
||||
$query = $builder->getQuery();
|
||||
$event = new ORMAdapterQueryEvent($query);
|
||||
$state->getDataTable()->getEventDispatcher()->dispatch($event, ORMAdapterEvents::PRE_QUERY);
|
||||
|
||||
foreach ($query->iterate([], $this->hydrationMode) as $result) {
|
||||
yield $entity = array_values($result)[0];
|
||||
if (Query::HYDRATE_OBJECT === $this->hydrationMode) {
|
||||
$this->manager->detach($entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function buildCriteria(QueryBuilder $queryBuilder, DataTableState $state)
|
||||
{
|
||||
foreach ($this->criteriaProcessors as $provider) {
|
||||
$provider->process($queryBuilder, $state);
|
||||
}
|
||||
}
|
||||
|
||||
protected function createQueryBuilder(DataTableState $state): QueryBuilder
|
||||
{
|
||||
/** @var QueryBuilder $queryBuilder */
|
||||
$queryBuilder = $this->manager->createQueryBuilder();
|
||||
|
||||
// Run all query builder processors in order
|
||||
foreach ($this->queryBuilderProcessors as $processor) {
|
||||
$processor->process($queryBuilder, $state);
|
||||
}
|
||||
|
||||
return $queryBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $identifier
|
||||
* @return int
|
||||
*/
|
||||
protected function getCount(QueryBuilder $queryBuilder, $identifier)
|
||||
{
|
||||
$qb = clone $queryBuilder;
|
||||
|
||||
$qb->resetDQLPart('orderBy');
|
||||
$gb = $qb->getDQLPart('groupBy');
|
||||
if (empty($gb) || !$this->hasGroupByPart($identifier, $gb)) {
|
||||
$qb->select($qb->expr()->count($identifier));
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
} else {
|
||||
$qb->resetDQLPart('groupBy');
|
||||
$qb->select($qb->expr()->countDistinct($identifier));
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $identifier
|
||||
* @param Query\Expr\GroupBy[] $gbList
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasGroupByPart($identifier, array $gbList)
|
||||
{
|
||||
foreach ($gbList as $gb) {
|
||||
if (in_array($identifier, $gb->getParts(), true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @return string
|
||||
*/
|
||||
protected function mapFieldToPropertyPath($field, array $aliases = [])
|
||||
{
|
||||
$parts = explode('.', $field);
|
||||
if (count($parts) < 2) {
|
||||
throw new InvalidConfigurationException(sprintf("Field name '%s' must consist at least of an alias and a field separated with a period", $field));
|
||||
}
|
||||
list($origin, $target) = $parts;
|
||||
|
||||
$path = [$target];
|
||||
$current = $aliases[$origin][0];
|
||||
|
||||
while (null !== $current) {
|
||||
list($origin, $target) = explode('.', $current);
|
||||
$path[] = $target;
|
||||
$current = $aliases[$origin][0];
|
||||
}
|
||||
|
||||
if (Query::HYDRATE_ARRAY === $this->hydrationMode) {
|
||||
return '[' . implode('][', array_reverse($path)) . ']';
|
||||
} else {
|
||||
return implode('.', array_reverse($path));
|
||||
}
|
||||
}
|
||||
|
||||
protected function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$providerNormalizer = function (Options $options, $value) {
|
||||
return array_map([$this, 'normalizeProcessor'], (array) $value);
|
||||
};
|
||||
|
||||
$resolver
|
||||
->setDefaults([
|
||||
'hydrate' => Query::HYDRATE_OBJECT,
|
||||
'query' => [],
|
||||
'criteria' => function (Options $options) {
|
||||
return [new SearchCriteriaProvider()];
|
||||
},
|
||||
])
|
||||
->setRequired('entity')
|
||||
->setAllowedTypes('entity', ['string'])
|
||||
->setAllowedTypes('hydrate', 'int')
|
||||
->setAllowedTypes('query', [QueryBuilderProcessorInterface::class, 'array', 'callable'])
|
||||
->setAllowedTypes('criteria', [QueryBuilderProcessorInterface::class, 'array', 'callable', 'null'])
|
||||
->setNormalizer('query', $providerNormalizer)
|
||||
->setNormalizer('criteria', $providerNormalizer)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|QueryBuilderProcessorInterface $provider
|
||||
* @return QueryBuilderProcessorInterface
|
||||
*/
|
||||
private function normalizeProcessor($provider)
|
||||
{
|
||||
if ($provider instanceof QueryBuilderProcessorInterface) {
|
||||
return $provider;
|
||||
} elseif (is_callable($provider)) {
|
||||
return new class($provider) implements QueryBuilderProcessorInterface {
|
||||
private $callable;
|
||||
|
||||
public function __construct(callable $value)
|
||||
{
|
||||
$this->callable = $value;
|
||||
}
|
||||
|
||||
public function process(QueryBuilder $queryBuilder, DataTableState $state)
|
||||
{
|
||||
return call_user_func($this->callable, $queryBuilder, $state);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
throw new InvalidConfigurationException('Provider must be a callable or implement QueryBuilderProcessorInterface');
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ declare(strict_types=1);
|
|||
namespace App\DataTables;
|
||||
|
||||
use App\DataTables\Adapter\CustomORMAdapter;
|
||||
use App\DataTables\Adapter\FetchJoinORMAdapter;
|
||||
use App\DataTables\Column\EntityColumn;
|
||||
use App\DataTables\Column\LocaleDateTimeColumn;
|
||||
use App\DataTables\Column\MarkdownColumn;
|
||||
|
@ -215,7 +216,8 @@ final class PartsDataTable implements DataTableTypeInterface
|
|||
])
|
||||
|
||||
->addOrderBy('name')
|
||||
->createAdapter(CustomORMAdapter::class, [
|
||||
->createAdapter(FetchJoinORMAdapter::class, [
|
||||
'simple_total_query' => true,
|
||||
'query' => function (QueryBuilder $builder): void {
|
||||
$this->getQuery($builder);
|
||||
},
|
||||
|
|
|
@ -36,6 +36,13 @@ Encore
|
|||
// only needed for CDN's or sub-directory deploy
|
||||
//.setManifestKeyPrefix('build/')
|
||||
|
||||
/**
|
||||
* If you are putting Part-DB into a sub directory you have to uncomment these lines and
|
||||
* replace "part-db/" with your path to Part-DB
|
||||
*/
|
||||
//.setPublicPath('/part-db/build')
|
||||
//.setManifestKeyPrefix('build/')
|
||||
|
||||
/*
|
||||
* ENTRY CONFIG
|
||||
*
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue