diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f12c4f06..99955059 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: - php-versions: [ '7.4', '8.0', '8.1', '8.2' ] + php-versions: [ '8.1', '8.2' ] db-type: [ 'mysql', 'sqlite' ] env: @@ -109,7 +109,7 @@ jobs: run: php bin/console --env test doctrine:migrations:migrate -n - name: Load fixtures - run: php bin/console --env test doctrine:fixtures:load -n --purger reset_autoincrement_purger + run: php bin/console --env test doctrine:fixtures:load -n - name: Run PHPunit and generate coverage run: ./bin/phpunit --coverage-clover=coverage.xml diff --git a/VERSION b/VERSION index 9df886c4..59b9db0c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.4.2 +1.5.0-dev diff --git a/assets/bootstrap.js b/assets/bootstrap.js index 58308a6b..c26293e2 100644 --- a/assets/bootstrap.js +++ b/assets/bootstrap.js @@ -4,8 +4,7 @@ import { startStimulusApp } from '@symfony/stimulus-bridge'; export const app = startStimulusApp(require.context( '@symfony/stimulus-bridge/lazy-controller-loader!./controllers', true, - /\.(j|t)sx?$/ + /\.[jt]sx?$/ )); - // register any custom, 3rd party controllers here // app.register('some_controller_name', SomeImportedController); diff --git a/assets/controllers/common/darkmode_controller.js b/assets/controllers/common/darkmode_controller.js index e7c18e67..71111166 100644 --- a/assets/controllers/common/darkmode_controller.js +++ b/assets/controllers/common/darkmode_controller.js @@ -18,43 +18,118 @@ */ import {Controller} from "@hotwired/stimulus"; -import Darkmode from "darkmode-js/src"; -import "darkmode-js" export default class extends Controller { - _darkmode; - connect() { - if (typeof window.getComputedStyle(document.body).mixBlendMode == 'undefined') { - console.warn("The browser does not support mix blend mode. Darkmode will not work."); + this.setMode(this.getMode()); + document.querySelectorAll('input[name="darkmode"]').forEach((radio) => { + radio.addEventListener('change', this._radioChanged.bind(this)); + }); + } + + /** + * Event listener for the change of radio buttons + * @private + */ + _radioChanged(event) { + const new_mode = this.getSelectedMode(); + this.setMode(new_mode); + } + + /** + * Get the current mode from the local storage + * @return {'dark', 'light', 'auto'} + */ + getMode() { + return localStorage.getItem('darkmode') ?? 'auto'; + } + + /** + * Set the mode in the local storage and apply it and change the state of the radio buttons + * @param mode + */ + setMode(mode) { + if (mode !== 'dark' && mode !== 'light' && mode !== 'auto') { + console.warn('Invalid darkmode mode: ' + mode); + mode = 'auto'; + } + + localStorage.setItem('darkmode', mode); + + this.setButtonMode(mode); + + if (mode === 'auto') { + this._setDarkmodeAuto(); + } else if (mode === 'dark') { + this._enableDarkmode(); + } else if (mode === 'light') { + this._disableDarkmode(); + } + } + + /** + * Get the selected mode via the radio buttons + * @return {'dark', 'light', 'auto'} + */ + getSelectedMode() { + return document.querySelector('input[name="darkmode"]:checked').value; + } + + /** + * Set the state of the radio buttons + * @param mode + */ + setButtonMode(mode) { + document.querySelector('input[name="darkmode"][value="' + mode + '"]').checked = true; + } + + /** + * Enable darkmode by adding the data-bs-theme="dark" to the html tag + * @private + */ + _enableDarkmode() { + //Add data-bs-theme="dark" to the html tag + document.documentElement.setAttribute('data-bs-theme', 'dark'); + } + + /** + * Disable darkmode by adding the data-bs-theme="light" to the html tag + * @private + */ + _disableDarkmode() { + //Set data-bs-theme to light + document.documentElement.setAttribute('data-bs-theme', 'light'); + } + + + /** + * Set the darkmode to auto and enable/disable it depending on the system settings, also add + * an event listener to change the darkmode if the system settings change + * @private + */ + _setDarkmodeAuto() { + if (this.getMode() !== 'auto') { return; } - try { - const darkmode = new Darkmode(); - this._darkmode = darkmode; - - //Unhide darkmode button - this._showWidget(); - - //Set the switch according to our current darkmode state - const toggler = document.getElementById("toggleDarkmode"); - toggler.checked = darkmode.isActivated(); - } - catch (e) - { - console.error(e); + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + this._enableDarkmode(); + } else { + this._disableDarkmode(); } - + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { + console.log('Prefered color scheme changed to ' + event.matches ? 'dark' : 'light'); + this._setDarkmodeAuto(); + }); } - _showWidget() { - this.element.classList.remove('hidden'); - } - - toggleDarkmode() { - this._darkmode.toggle(); + /** + * Check if darkmode is activated + * @return {boolean} + */ + isDarkmodeActivated() { + return document.documentElement.getAttribute('data-bs-theme') === 'dark'; } } \ No newline at end of file diff --git a/assets/controllers/common/markdown_controller.js b/assets/controllers/common/markdown_controller.js index 08c013ca..91aaef66 100644 --- a/assets/controllers/common/markdown_controller.js +++ b/assets/controllers/common/markdown_controller.js @@ -21,6 +21,8 @@ import { Controller } from '@hotwired/stimulus'; import { marked } from "marked"; +import { mangle } from "marked-mangle"; +import { gfmHeadingId } from "marked-gfm-heading-id"; import DOMPurify from 'dompurify'; import "../../css/app/markdown.css"; @@ -81,6 +83,10 @@ export default class extends Controller { */ configureMarked() { + marked.use(mangle()); + marked.use(gfmHeadingId({ + })); + marked.setOptions({ gfm: true, }); diff --git a/assets/controllers/elements/datatables/datatables_controller.js b/assets/controllers/elements/datatables/datatables_controller.js index 67e8ef6e..f9dc447e 100644 --- a/assets/controllers/elements/datatables/datatables_controller.js +++ b/assets/controllers/elements/datatables/datatables_controller.js @@ -97,7 +97,7 @@ export default class extends Controller { }, buttons: [{ "extend": 'colvis', - 'className': 'mr-2 btn-light', + 'className': 'mr-2 btn-outline-secondary', 'columns': ':not(.no-colvis)', "text": "" }], @@ -123,6 +123,22 @@ export default class extends Controller { console.error("Error initializing datatables: " + err); }); + //Fix height of the length selector + promise.then((dt) => { + //Find all length selectors (select with name dt_length), which are inside a label + const lengthSelectors = document.querySelectorAll('label select[name="dt_length"]'); + //And remove the surrounding label, while keeping the select with all event handlers + lengthSelectors.forEach((selector) => { + selector.parentElement.replaceWith(selector); + }); + + //Find all column visibility buttons (button with buttons-colvis class) and remove the btn-secondary class + const colVisButtons = document.querySelectorAll('button.buttons-colvis'); + colVisButtons.forEach((button) => { + button.classList.remove('btn-secondary'); + }); + }); + //Dispatch an event to let others know that the datatables has been loaded promise.then((dt) => { const event = new CustomEvent(EVENT_DT_LOADED, {bubbles: true}); diff --git a/assets/controllers/elements/tree_controller.js b/assets/controllers/elements/tree_controller.js index bdb150c2..d2f21a8e 100644 --- a/assets/controllers/elements/tree_controller.js +++ b/assets/controllers/elements/tree_controller.js @@ -81,6 +81,14 @@ export default class extends Controller { this._tree.remove(); } + const BS53Theme = { + getOptions() { + return { + onhoverColor: 'var(--bs-secondary-bg)', + }; + } + } + this._tree = new BSTreeView(this.treeTarget, { levels: 1, showTags: this._showTags, @@ -93,7 +101,7 @@ export default class extends Controller { } }, //onNodeContextmenu: contextmenu_handler, - }, [BS5Theme, FAIconTheme]); + }, [BS5Theme, BS53Theme, FAIconTheme]); this.treeTarget.addEventListener(EVENT_INITIALIZED, (event) => { /** @type {BSTreeView} */ diff --git a/assets/css/app/bs-overrides.css b/assets/css/app/bs-overrides.css index 61a36bc4..070f353d 100644 --- a/assets/css/app/bs-overrides.css +++ b/assets/css/app/bs-overrides.css @@ -99,7 +99,7 @@ label:not(.form-check-label, .custom-control-label) { form .col-form-label.required:after, form label.required:after { bottom: 4px; - color: var(--bs-dark); + color: var(--bs-secondary-color); content: "\2022"; filter: opacity(75%); position: relative; diff --git a/assets/css/app/helpers.css b/assets/css/app/helpers.css index acc2682e..db9a02f4 100644 --- a/assets/css/app/helpers.css +++ b/assets/css/app/helpers.css @@ -79,7 +79,7 @@ ul.structural_link li { /* Add a slash symbol (/) before/behind each list item */ ul.structural_link li+li:before { padding: 2px; - color: grey; + color: var(--bs-tertiary-color); /*content: "/\00a0";*/ font-family: "Font Awesome 5 Free"; font-weight: 900; @@ -89,13 +89,13 @@ ul.structural_link li+li:before { /* Add a color to all links inside the list */ ul.structural_link li a { - color: #0275d8; + color: var(--bs-link-color); text-decoration: none; } /* Add a color on mouse-over */ ul.structural_link li a:hover { - color: #01447e; + color: var(--bs-link-hover-color); text-decoration: underline; } diff --git a/assets/css/app/layout.css b/assets/css/app/layout.css index 059fc131..e00c823c 100644 --- a/assets/css/app/layout.css +++ b/assets/css/app/layout.css @@ -78,8 +78,6 @@ body { overflow: -moz-scrollbars-none; /* Use standard version for hiding the scrollbar */ scrollbar-width: none; - - background-color: var(--light); } #sidebar-container { diff --git a/assets/css/app/tables.css b/assets/css/app/tables.css index ef257a55..276272b7 100644 --- a/assets/css/app/tables.css +++ b/assets/css/app/tables.css @@ -91,7 +91,7 @@ th.select-checkbox { /** Fix datatables select-checkbox position */ table.dataTable tr.selected td.select-checkbox:after { - margin-top: -28px !important; + margin-top: -25px !important; } @@ -116,23 +116,33 @@ table.dataTable > thead > tr > th.select-checkbox:before, table.dataTable > thead > tr > th.select-checkbox:after { display: block; position: absolute; - top: 1.2em; + top: 0.9em; left: 50%; - width: 12px; - height: 12px; + width: 1em !important; + height: 1em !important; box-sizing: border-box; } table.dataTable > thead > tr > th.select-checkbox:before { content: " "; margin-top: -5px; margin-left: -6px; - border: 1px solid black; + border: 2px solid var(--bs-tertiary-color); border-radius: 3px; } + +table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:before { + border: 2px solid var(--bs-tertiary-color) !important; +} + +table.dataTable > tbody > tr > td.select-checkbox:before, table.dataTable > tbody > tr > td.select-checkbox:after, table.dataTable > tbody > tr > th.select-checkbox:before, table.dataTable > tbody > tr > th.select-checkbox:after { + width: 1em !important; + height: 1em !important; +} + table.dataTable > thead > tr.selected > th.select-checkbox:after { content: "✓"; font-size: 20px; - margin-top: -23px; + margin-top: -20px; margin-left: -6px; text-align: center; /*text-shadow: 1px 1px #B0BED9, -1px -1px #B0BED9, 1px -1px #B0BED9, -1px 1px #B0BED9; */ diff --git a/assets/css/components/ckeditor.css b/assets/css/components/ckeditor.css index 1904e701..f8a682a2 100644 --- a/assets/css/components/ckeditor.css +++ b/assets/css/components/ckeditor.css @@ -36,3 +36,42 @@ .ck-html-label .ck-content hr { margin: 2px; } + +/*********************************************** + * Hide CKEditor powered by message + ***********************************************/ +.ck-powered-by { + display: none; +} + + + + +/*********************************************** + * Use Bootstrap color vars for CKEditor + ***********************************************/ +:root { + --ck-color-base-foreground: var(--bs-secondary-bg); + --ck-color-base-background: var(--bs-body-bg); + --ck-color-base-border: var(--bs-border-color); + --ck-color-base-action: var(--bs-success); + --ck-color-base-focus: var(--bs-primary-border-subtle); + --ck-color-base-text: var(--bs-body-color); + --ck-color-base-active: var(--bs-primary-bg-subtle); + --ck-color-base-active-focus: var(--bs-primary); + --ck-color-base-error: var(--bs-danger); + + /* Improve contrast between text and toolbar */ + --ck-color-toolbar-background: var(--bs-tertiary-bg); + + /* Buttons */ + --ck-color-button-default-hover-background: var(--bs-secondary-bg); + --ck-color-button-default-active-background: var(--bs-secondary-bg); + + --ck-color-button-on-background: var(--bs-body-bg); + --ck-color-button-on-hover-background: var(--bs-secondary-bg); + --ck-color-button-on-active-background: var(--bs-secondary-bg); + --ck-color-button-on-disabled-background: var(--bs-secondary-bg); + --ck-color-button-on-color: var(--bs-primary) + +} \ No newline at end of file diff --git a/assets/css/components/tom-select_extensions.css b/assets/css/components/tom-select_extensions.css index 326731c9..abac05b5 100644 --- a/assets/css/components/tom-select_extensions.css +++ b/assets/css/components/tom-select_extensions.css @@ -18,6 +18,24 @@ */ .tagsinput.ts-wrapper.multi .ts-control > div { - background: var(--bs-secondary); - color: var(--bs-white); -} \ No newline at end of file + background: var(--bs-secondary-bg); + color: var(--bs-body-color); +} + +/********* + * BS 5.3 compatible dark mode + ***************/ + +.ts-dropdown .active { + background-color: var(--bs-secondary-bg) !important; + color: var(--bs-body-color) !important; +} + +.ts-dropdown, .ts-control, .ts-control input { + color: var(--bs-body-color) !important; +} + +.ts-dropdown, .ts-dropdown.form-control, .ts-dropdown.form-select { + background: var(--bs-body-bg); +} + diff --git a/assets/js/app.js b/assets/js/app.js index 8242331f..fd7db935 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -22,7 +22,6 @@ import '../css/app/layout.css'; import '../css/app/helpers.css'; -import '../css/app/darkmode.css'; import '../css/app/tables.css'; import '../css/app/bs-overrides.css'; import '../css/app/treeview.css'; diff --git a/assets/js/register_events.js b/assets/js/register_events.js index 06f48ec1..d9b21ee9 100644 --- a/assets/js/register_events.js +++ b/assets/js/register_events.js @@ -62,7 +62,7 @@ class RegisterEventHelper { this.registerLoadHandler(() => { $(".tooltip").remove(); //Exclude dropdown buttons from tooltips, otherwise we run into endless errors from bootstrap (bootstrap.esm.js:614 Bootstrap doesn't allow more than one instance per element. Bound instance: bs.dropdown.) - $('a[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i.fas[title]') + $('a[title], label[title], button[title]:not([data-bs-toggle="dropdown"]), p[title], span[title], h6[title], h3[title], i.fas[title]') //@ts-ignore .tooltip("hide").tooltip({container: "body", placement: "auto", boundary: 'window'}); }); diff --git a/assets/js/webauthn_tfa.js b/assets/js/webauthn_tfa.js index a2e00595..4d54efc0 100644 --- a/assets/js/webauthn_tfa.js +++ b/assets/js/webauthn_tfa.js @@ -21,8 +21,13 @@ class WebauthnTFA { -// Decodes a Base64Url string - _base64UrlDecode = (input) => { + _b64UrlSafeEncode = (str) => { + const b64 = btoa(str); + return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); + } + + // Decodes a Base64Url string + _b64UrlSafeDecode = (input) => { input = input .replace(/-/g, '+') .replace(/_/g, '/'); @@ -39,13 +44,16 @@ class WebauthnTFA { }; // Converts an array of bytes into a Base64Url string - _arrayToBase64String = (a) => btoa(String.fromCharCode(...a)); + _arrayToBase64String = (a) => { + const str = String.fromCharCode(...a); + return this._b64UrlSafeEncode(str); + } // Prepares the public key options object returned by the Webauthn Framework _preparePublicKeyOptions = publicKey => { //Convert challenge from Base64Url string to Uint8Array publicKey.challenge = Uint8Array.from( - this._base64UrlDecode(publicKey.challenge), + this._b64UrlSafeDecode(publicKey.challenge), c => c.charCodeAt(0) ); @@ -67,7 +75,7 @@ class WebauthnTFA { return { ...data, id: Uint8Array.from( - this._base64UrlDecode(data.id), + this._b64UrlSafeDecode(data.id), c => c.charCodeAt(0) ), }; @@ -81,7 +89,7 @@ class WebauthnTFA { return { ...data, id: Uint8Array.from( - this._base64UrlDecode(data.id), + this._b64UrlSafeDecode(data.id), c => c.charCodeAt(0) ), }; diff --git a/composer.json b/composer.json index 0f5c6450..a9094483 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "type": "project", "license": "AGPL-3.0-or-later", "require": { - "php": "^7.4 || ^8.0", + "php": "^8.1", "ext-ctype": "*", "ext-dom": "*", "ext-gd": "*", @@ -11,9 +11,9 @@ "ext-json": "*", "ext-mbstring": "*", "beberlei/doctrineextensions": "^1.2", - "brick/math": "^0.8.15", - "composer/package-versions-deprecated": "1.11.99.4", - "doctrine/annotations": "^1.6", + "brick/math": "^0.11.0", + "composer/package-versions-deprecated": "^1.11.99.5", + "doctrine/annotations": "1.14.3", "doctrine/data-fixtures": "^1.6.6", "doctrine/dbal": "^3.4.6", "doctrine/doctrine-bundle": "^2.0", @@ -24,54 +24,53 @@ "florianv/swap": "^4.0", "florianv/swap-bundle": "dev-master", "gregwar/captcha-bundle": "^2.1.0", - "hslavich/oneloginsaml-bundle": "^2.10", - "jbtronics/2fa-webauthn": "^1.0.0", + "jbtronics/2fa-webauthn": "^v2.0.0", "jfcherng/php-diff": "^6.14", "league/csv": "^9.8.0", "league/html-to-markdown": "^5.0.1", "liip/imagine-bundle": "^2.2", + "nbgrp/onelogin-saml-bundle": "^1.3", "nelexa/zip": "^4.0", "nelmio/security-bundle": "^3.0", "nyholm/psr7": "^1.1", "ocramius/proxy-manager": "2.2.*", - "omines/datatables-bundle": "^0.5.0", + "omines/datatables-bundle": "^0.7.2", "php-translation/symfony-bundle": "^0.13.0", "phpdocumentor/reflection-docblock": "^5.2", "s9e/text-formatter": "^2.1", - "scheb/2fa-backup-code": "^5.13", - "scheb/2fa-bundle": "^5.13", - "scheb/2fa-google-authenticator": "^5.13", - "scheb/2fa-trusted-device": "^5.13", - "sensio/framework-extra-bundle": "^6.1.1", + "scheb/2fa-backup-code": "^6.8.0", + "scheb/2fa-bundle": "^6.8.0", + "scheb/2fa-google-authenticator": "^6.8.0", + "scheb/2fa-trusted-device": "^6.8.0", "shivas/versioning-bundle": "^4.0", - "spatie/db-dumper": "^2.21", + "spatie/db-dumper": "^3.3.1", "symfony/apache-pack": "^1.0", - "symfony/asset": "5.4.*", - "symfony/console": "5.4.*", - "symfony/dotenv": "5.4.*", - "symfony/expression-language": "5.4.*", - "symfony/flex": "^1.1", - "symfony/form": "5.4.*", - "symfony/framework-bundle": "5.4.*", - "symfony/http-client": "5.4.*", - "symfony/http-kernel": "5.4.*", - "symfony/mailer": "5.4.*", + "symfony/asset": "6.3.*", + "symfony/console": "6.3.*", + "symfony/dotenv": "6.3.*", + "symfony/expression-language": "6.3.*", + "symfony/flex": "^v2.3.1", + "symfony/form": "6.3.*", + "symfony/framework-bundle": "6.3.*", + "symfony/http-client": "6.3.*", + "symfony/http-kernel": "6.3.*", + "symfony/mailer": "6.3.*", "symfony/monolog-bundle": "^3.1", - "symfony/process": "5.4.*", - "symfony/property-access": "5.4.*", - "symfony/property-info": "5.4.*", - "symfony/proxy-manager-bridge": "5.4.*", - "symfony/rate-limiter": "5.4.*", - "symfony/runtime": "5.4.*", - "symfony/security-bundle": "5.4.*", - "symfony/serializer": "5.4.*", - "symfony/translation": "5.4.*", - "symfony/twig-bundle": "5.4.*", + "symfony/process": "6.3.*", + "symfony/property-access": "6.3.*", + "symfony/property-info": "6.3.*", + "symfony/proxy-manager-bridge": "6.3.*", + "symfony/rate-limiter": "6.3.*", + "symfony/runtime": "6.3.*", + "symfony/security-bundle": "6.3.*", + "symfony/serializer": "6.3.*", + "symfony/translation": "6.3.*", + "symfony/twig-bundle": "6.3.*", "symfony/ux-turbo": "^2.0", - "symfony/validator": "5.4.*", - "symfony/web-link": "5.4.*", - "symfony/webpack-encore-bundle": "^1.1", - "symfony/yaml": "5.4.*", + "symfony/validator": "6.3.*", + "symfony/web-link": "6.3.*", + "symfony/webpack-encore-bundle": "^v2.0.1", + "symfony/yaml": "6.3.*", "tecnickcom/tc-lib-barcode": "^1.15", "twig/cssinliner-extra": "^3.0", "twig/extra-bundle": "^3.0", @@ -79,28 +78,30 @@ "twig/inky-extra": "^3.0", "twig/intl-extra": "^3.0", "twig/markdown-extra": "^3.0", - "web-auth/webauthn-symfony-bundle": "^3.3", + "web-auth/webauthn-symfony-bundle": "^4.0.0", "webmozart/assert": "^1.4" }, "require-dev": { "dama/doctrine-test-bundle": "^7.0", + "doctrine/doctrine-fixtures-bundle": "^3.2", "ekino/phpstan-banned-code": "^v1.0.0", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.4.7", "phpstan/phpstan-doctrine": "^1.2.11", + "phpstan/phpstan-strict-rules": "^1.5", "phpstan/phpstan-symfony": "^1.1.7", "psalm/plugin-symfony": "^v5.0.1", + "rector/rector": "^0.17.0", "roave/security-advisories": "dev-latest", - "symfony/browser-kit": "^5.2", - "symfony/css-selector": "^5.2", - "symfony/debug-bundle": "^5.2", + "symfony/browser-kit": "6.3.*", + "symfony/css-selector": "6.3.*", + "symfony/debug-bundle": "6.3.*", "symfony/maker-bundle": "^1.13", - "symfony/phpunit-bridge": "5.4.*", - "symfony/stopwatch": "^5.2", - "symfony/web-profiler-bundle": "^5.2", + "symfony/phpunit-bridge": "6.3.*", + "symfony/stopwatch": "6.3.*", + "symfony/web-profiler-bundle": "6.3.*", "symplify/easy-coding-standard": "^11.0", - "vimeo/psalm": "^5.6.0", - "doctrine/doctrine-fixtures-bundle": "^3.2" + "vimeo/psalm": "^5.6.0" }, "suggest": { "ext-bcmath": "Used to improve price calculation performance", @@ -111,7 +112,7 @@ "*": "dist" }, "platform": { - "php": "7.4.0" + "php": "8.1.0" }, "sort-packages": true, "allow-plugins": { @@ -143,7 +144,7 @@ "post-update-cmd": [ "@auto-scripts" ], - "phpstan": "vendor/bin/phpstan analyse src --level 2 --memory-limit 1G" + "phpstan": "vendor/bin/phpstan analyse src --level 5 --memory-limit 1G" }, "conflict": { "symfony/symfony": "*" @@ -151,9 +152,7 @@ "extra": { "symfony": { "allow-contrib": false, - "require": "5.4.*" + "require": "6.3.*" } - }, - "repositories": [ - ] + } } diff --git a/composer.lock b/composer.lock index a7cb642f..ca9d4692 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "51206f9aa7e372b40ef62a105c4a3f13", + "content-hash": "a8ee56e1ebdd203018f921be240029ca", "packages": [ { "name": "beberlei/assert", @@ -132,26 +132,25 @@ }, { "name": "brick/math", - "version": "0.8.17", + "version": "0.11.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "e6f8e7d04346a95be89580f8c2c22d6c3fa65556" + "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/e6f8e7d04346a95be89580f8c2c22d6c3fa65556", - "reference": "e6f8e7d04346a95be89580f8c2c22d6c3fa65556", + "url": "https://api.github.com/repos/brick/math/zipball/0ad82ce168c82ba30d1c01ec86116ab52f589478", + "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478", "shasum": "" }, "require": { - "ext-json": "*", - "php": "^7.1|^8.0" + "php": "^8.0" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^7.5.15|^8.5", - "vimeo/psalm": "^3.5" + "phpunit/phpunit": "^9.0", + "vimeo/psalm": "5.0.0" }, "type": "library", "autoload": { @@ -176,15 +175,15 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/v0.8" + "source": "https://github.com/brick/math/tree/0.11.0" }, "funding": [ { - "url": "https://tidelift.com/funding/github/packagist/brick/math", - "type": "tidelift" + "url": "https://github.com/BenMorel", + "type": "github" } ], - "time": "2020-08-18T23:41:20+00:00" + "time": "2023-01-15T23:15:59+00:00" }, { "name": "composer/ca-bundle", @@ -264,16 +263,16 @@ }, { "name": "composer/package-versions-deprecated", - "version": "1.11.99.4", + "version": "1.11.99.5", "source": { "type": "git", "url": "https://github.com/composer/package-versions-deprecated.git", - "reference": "b174585d1fe49ceed21928a945138948cb394600" + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b174585d1fe49ceed21928a945138948cb394600", - "reference": "b174585d1fe49ceed21928a945138948cb394600", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", "shasum": "" }, "require": { @@ -317,7 +316,7 @@ "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { "issues": "https://github.com/composer/package-versions-deprecated/issues", - "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.4" + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" }, "funding": [ { @@ -333,7 +332,7 @@ "type": "tidelift" } ], - "time": "2021-09-13T08:41:34+00:00" + "time": "2022-01-17T14:14:24+00:00" }, { "name": "doctrine/annotations", @@ -506,32 +505,34 @@ }, { "name": "doctrine/collections", - "version": "1.8.0", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "2b44dd4cbca8b5744327de78bafef5945c7e7b5e" + "reference": "db8cda536a034337f7dd63febecc713d4957f9ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/2b44dd4cbca8b5744327de78bafef5945c7e7b5e", - "reference": "2b44dd4cbca8b5744327de78bafef5945c7e7b5e", + "url": "https://api.github.com/repos/doctrine/collections/zipball/db8cda536a034337f7dd63febecc713d4957f9ee", + "reference": "db8cda536a034337f7dd63febecc713d4957f9ee", "shasum": "" }, "require": { - "doctrine/deprecations": "^0.5.3 || ^1", - "php": "^7.1.3 || ^8.0" + "doctrine/deprecations": "^1", + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9.0 || ^10.0", - "phpstan/phpstan": "^1.4.8", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.1.5", + "doctrine/coding-standard": "^10.0", + "ext-json": "*", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5", "vimeo/psalm": "^4.22" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + "Doctrine\\Common\\Collections\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -570,9 +571,23 @@ ], "support": { "issues": "https://github.com/doctrine/collections/issues", - "source": "https://github.com/doctrine/collections/tree/1.8.0" + "source": "https://github.com/doctrine/collections/tree/2.1.2" }, - "time": "2022-09-01T20:12:10+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcollections", + "type": "tidelift" + } + ], + "time": "2022-12-27T23:41:38+00:00" }, { "name": "doctrine/common", @@ -749,16 +764,16 @@ }, { "name": "doctrine/dbal", - "version": "3.6.3", + "version": "3.6.4", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "9a747d29e7e6b39509b8f1847e37a23a0163ea6a" + "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/9a747d29e7e6b39509b8f1847e37a23a0163ea6a", - "reference": "9a747d29e7e6b39509b8f1847e37a23a0163ea6a", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f", + "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f", "shasum": "" }, "require": { @@ -841,7 +856,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.3" + "source": "https://github.com/doctrine/dbal/tree/3.6.4" }, "funding": [ { @@ -857,7 +872,7 @@ "type": "tidelift" } ], - "time": "2023-06-01T05:46:46+00:00" + "time": "2023-06-15T07:40:12+00:00" }, { "name": "doctrine/deprecations", @@ -1109,30 +1124,29 @@ }, { "name": "doctrine/event-manager", - "version": "1.2.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520" + "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/95aa4cb529f1e96576f3fda9f5705ada4056a520", - "reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/750671534e0241a7c50ea5b43f67e23eb5c96f32", + "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32", "shasum": "" }, "require": { - "doctrine/deprecations": "^0.5.3 || ^1", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "conflict": { "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "~1.4.10 || ^1.8.8", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.24" + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.28" }, "type": "library", "autoload": { @@ -1181,7 +1195,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/1.2.0" + "source": "https://github.com/doctrine/event-manager/tree/2.0.0" }, "funding": [ { @@ -1197,32 +1211,32 @@ "type": "tidelift" } ], - "time": "2022-10-12T20:51:15+00:00" + "time": "2022-10-12T20:59:15+00:00" }, { "name": "doctrine/inflector", - "version": "2.0.6", + "version": "2.0.8", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024" + "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", - "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/f9301a5b2fb1216b2b08f02ba04dc45423db6bff", + "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^10", + "doctrine/coding-standard": "^11.0", "phpstan/phpstan": "^1.8", "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.3", "phpunit/phpunit": "^8.5 || ^9.5", - "vimeo/psalm": "^4.25" + "vimeo/psalm": "^4.25 || ^5.4" }, "type": "library", "autoload": { @@ -1272,7 +1286,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.6" + "source": "https://github.com/doctrine/inflector/tree/2.0.8" }, "funding": [ { @@ -1288,34 +1302,34 @@ "type": "tidelift" } ], - "time": "2022-10-20T09:10:12+00:00" + "time": "2023-06-16T13:40:37+00:00" }, { "name": "doctrine/instantiator", - "version": "1.5.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^11", + "doctrine/coding-standard": "^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^5.4" }, "type": "library", "autoload": { @@ -1342,7 +1356,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + "source": "https://github.com/doctrine/instantiator/tree/2.0.0" }, "funding": [ { @@ -1358,7 +1372,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:15:36+00:00" + "time": "2022-12-30T00:23:10+00:00" }, { "name": "doctrine/lexer", @@ -1440,16 +1454,16 @@ }, { "name": "doctrine/migrations", - "version": "3.5.5", + "version": "3.6.0", "source": { "type": "git", "url": "https://github.com/doctrine/migrations.git", - "reference": "4b1e2b6ba71d21d0c5be22ed03b6fc954d20b204" + "reference": "e542ad8bcd606d7a18d0875babb8a6d963c9c059" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/migrations/zipball/4b1e2b6ba71d21d0c5be22ed03b6fc954d20b204", - "reference": "4b1e2b6ba71d21d0c5be22ed03b6fc954d20b204", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/e542ad8bcd606d7a18d0875babb8a6d963c9c059", + "reference": "e542ad8bcd606d7a18d0875babb8a6d963c9c059", "shasum": "" }, "require": { @@ -1457,11 +1471,11 @@ "doctrine/dbal": "^3.5.1", "doctrine/deprecations": "^0.5.3 || ^1", "doctrine/event-manager": "^1.2 || ^2.0", - "friendsofphp/proxy-manager-lts": "^1.0", - "php": "^7.4 || ^8.0", + "php": "^8.1", "psr/log": "^1.1.3 || ^2 || ^3", "symfony/console": "^4.4.16 || ^5.4 || ^6.0", - "symfony/stopwatch": "^4.4 || ^5.4 || ^6.0" + "symfony/stopwatch": "^4.4 || ^5.4 || ^6.0", + "symfony/var-exporter": "^6.2" }, "conflict": { "doctrine/orm": "<2.12" @@ -1477,7 +1491,7 @@ "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.1", "phpstan/phpstan-symfony": "^1.1", - "phpunit/phpunit": "^9.5", + "phpunit/phpunit": "^9.5.24", "symfony/cache": "^4.4 || ^5.4 || ^6.0", "symfony/process": "^4.4 || ^5.4 || ^6.0", "symfony/yaml": "^4.4 || ^5.4 || ^6.0" @@ -1522,7 +1536,7 @@ ], "support": { "issues": "https://github.com/doctrine/migrations/issues", - "source": "https://github.com/doctrine/migrations/tree/3.5.5" + "source": "https://github.com/doctrine/migrations/tree/3.6.0" }, "funding": [ { @@ -1538,20 +1552,20 @@ "type": "tidelift" } ], - "time": "2023-01-18T12:44:30+00:00" + "time": "2023-02-15T18:49:46+00:00" }, { "name": "doctrine/orm", - "version": "2.15.2", + "version": "2.15.3", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "bf449bef7ddc47e62c22f9a06dacc1736abe1c0b" + "reference": "4c3bd208018c26498e5f682aaad45fa00ea307d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/bf449bef7ddc47e62c22f9a06dacc1736abe1c0b", - "reference": "bf449bef7ddc47e62c22f9a06dacc1736abe1c0b", + "url": "https://api.github.com/repos/doctrine/orm/zipball/4c3bd208018c26498e5f682aaad45fa00ea307d5", + "reference": "4c3bd208018c26498e5f682aaad45fa00ea307d5", "shasum": "" }, "require": { @@ -1580,14 +1594,14 @@ "doctrine/annotations": "^1.13 || ^2", "doctrine/coding-standard": "^9.0.2 || ^12.0", "phpbench/phpbench": "^0.16.10 || ^1.0", - "phpstan/phpstan": "~1.4.10 || 1.10.14", + "phpstan/phpstan": "~1.4.10 || 1.10.18", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", "psr/log": "^1 || ^2 || ^3", "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^4.4 || ^5.4 || ^6.0", "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2", "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "vimeo/psalm": "4.30.0 || 5.11.0" + "vimeo/psalm": "4.30.0 || 5.12.0" }, "suggest": { "ext-dom": "Provides support for XSD validation for XML mapping files", @@ -1637,9 +1651,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/2.15.2" + "source": "https://github.com/doctrine/orm/tree/2.15.3" }, - "time": "2023-06-01T09:35:50+00:00" + "time": "2023-06-22T12:36:06+00:00" }, { "name": "doctrine/persistence", @@ -1855,26 +1869,26 @@ }, { "name": "egulias/email-validator", - "version": "3.2.6", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7" + "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7", - "reference": "e5997fa97e8790cdae03a9cbd5e78e45e3c7bda7", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/3a85486b709bc384dae8eb78fb2eec649bdb64ff", + "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff", "shasum": "" }, "require": { - "doctrine/lexer": "^1.2|^2", - "php": ">=7.2", - "symfony/polyfill-intl-idn": "^1.15" + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" }, "require-dev": { - "phpunit/phpunit": "^8.5.8|^9.3.3", - "vimeo/psalm": "^4" + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^4.30" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -1882,7 +1896,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0.x-dev" } }, "autoload": { @@ -1910,7 +1924,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/3.2.6" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.1" }, "funding": [ { @@ -1918,7 +1932,7 @@ "type": "github" } ], - "time": "2023-06-01T07:04:22+00:00" + "time": "2023-01-14T14:17:03+00:00" }, { "name": "erusev/parsedown", @@ -1970,82 +1984,6 @@ }, "time": "2019-12-30T22:54:17+00:00" }, - { - "name": "fgrosse/phpasn1", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/fgrosse/PHPASN1.git", - "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/42060ed45344789fb9f21f9f1864fc47b9e3507b", - "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "php-coveralls/php-coveralls": "~2.0", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" - }, - "suggest": { - "ext-bcmath": "BCmath is the fallback extension for big integer calculations", - "ext-curl": "For loading OID information from the web if they have not bee defined statically", - "ext-gmp": "GMP is the preferred extension for big integer calculations", - "phpseclib/bcmath_compat": "BCmath polyfill for servers where neither GMP nor BCmath is available" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "FG\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Friedrich Große", - "email": "friedrich.grosse@gmail.com", - "homepage": "https://github.com/FGrosse", - "role": "Author" - }, - { - "name": "All contributors", - "homepage": "https://github.com/FGrosse/PHPASN1/contributors" - } - ], - "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", - "homepage": "https://github.com/FGrosse/PHPASN1", - "keywords": [ - "DER", - "asn.1", - "asn1", - "ber", - "binary", - "decoding", - "encoding", - "x.509", - "x.690", - "x509", - "x690" - ], - "support": { - "issues": "https://github.com/fgrosse/PHPASN1/issues", - "source": "https://github.com/fgrosse/PHPASN1/tree/v2.5.0" - }, - "abandoned": true, - "time": "2022-12-19T11:08:26+00:00" - }, { "name": "florianv/exchanger", "version": "2.8.0", @@ -2452,67 +2390,6 @@ }, "time": "2022-01-11T08:28:06+00:00" }, - { - "name": "hslavich/oneloginsaml-bundle", - "version": "v2.10.0", - "source": { - "type": "git", - "url": "https://github.com/hslavich/OneloginSamlBundle.git", - "reference": "aee3450bd36b750a2e61b4a0ca19a09ecab7a086" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/hslavich/OneloginSamlBundle/zipball/aee3450bd36b750a2e61b4a0ca19a09ecab7a086", - "reference": "aee3450bd36b750a2e61b4a0ca19a09ecab7a086", - "shasum": "" - }, - "require": { - "onelogin/php-saml": "^3.0", - "symfony/dependency-injection": "^5.4", - "symfony/deprecation-contracts": "^2.1 | ^3", - "symfony/event-dispatcher-contracts": "^2.4", - "symfony/framework-bundle": "^5.4", - "symfony/security-bundle": "^5.4" - }, - "require-dev": { - "dms/phpunit-arraysubset-asserts": "^0.2.0", - "doctrine/orm": "~2.3", - "php-coveralls/php-coveralls": "^2.2", - "phpunit/phpunit": "^9.0", - "symfony/event-dispatcher": "^5.4", - "symfony/phpunit-bridge": "^5.4" - }, - "type": "symfony-bundle", - "autoload": { - "psr-4": { - "Hslavich\\OneloginSamlBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "hslavich", - "email": "hernan.slavich@gmail.com" - } - ], - "description": "OneLogin SAML Bundle for Symfony", - "keywords": [ - "SSO", - "onelogin", - "saml" - ], - "support": { - "issues": "https://github.com/hslavich/OneloginSamlBundle/issues", - "source": "https://github.com/hslavich/OneloginSamlBundle/tree/v2.10.0" - }, - "time": "2022-11-23T17:12:47+00:00" - }, { "name": "imagine/imagine", "version": "1.3.5", @@ -2577,29 +2454,31 @@ }, { "name": "jbtronics/2fa-webauthn", - "version": "v1.0.0", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/jbtronics/2fa-webauthn.git", - "reference": "c4108d16ba7a3061d977fc92f577c69067e1d003" + "reference": "807ae06ccbc73a17983ea80b5a2e4cc78c07f8ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jbtronics/2fa-webauthn/zipball/c4108d16ba7a3061d977fc92f577c69067e1d003", - "reference": "c4108d16ba7a3061d977fc92f577c69067e1d003", + "url": "https://api.github.com/repos/jbtronics/2fa-webauthn/zipball/807ae06ccbc73a17983ea80b5a2e4cc78c07f8ad", + "reference": "807ae06ccbc73a17983ea80b5a2e4cc78c07f8ad", "shasum": "" }, "require": { "ext-json": "*", "nyholm/psr7": "^1.5", - "php": "^7.4.0|^8.0", - "scheb/2fa-bundle": "^5.0.0|^6.0.0", - "symfony/framework-bundle": "^5.0|^6.0", + "php": "^8.1", + "scheb/2fa-bundle": "^6.0.0", + "symfony/framework-bundle": "^6.0", "symfony/psr-http-message-bridge": "^2.1", - "web-auth/webauthn-lib": "^3.3" + "symfony/uid": "^6.0", + "web-auth/webauthn-lib": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^9.5", + "roave/security-advisories": "dev-latest" }, "type": "symfony-bundle", "autoload": { @@ -2628,9 +2507,9 @@ ], "support": { "issues": "https://github.com/jbtronics/2fa-webauthn/issues", - "source": "https://github.com/jbtronics/2fa-webauthn/tree/v1.0.0" + "source": "https://github.com/jbtronics/2fa-webauthn/tree/v2.0.0" }, - "time": "2022-10-03T22:29:32+00:00" + "time": "2023-06-11T11:09:45+00:00" }, { "name": "jfcherng/php-color-output", @@ -2693,16 +2572,16 @@ }, { "name": "jfcherng/php-diff", - "version": "6.15.2", + "version": "6.15.3", "source": { "type": "git", "url": "https://github.com/jfcherng/php-diff.git", - "reference": "2c7c87f65b04f447b773fc297217b17cb6bb3787" + "reference": "39be09756f8eda115299add3f34dc64b4bc32b66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jfcherng/php-diff/zipball/2c7c87f65b04f447b773fc297217b17cb6bb3787", - "reference": "2c7c87f65b04f447b773fc297217b17cb6bb3787", + "url": "https://api.github.com/repos/jfcherng/php-diff/zipball/39be09756f8eda115299add3f34dc64b4bc32b66", + "reference": "39be09756f8eda115299add3f34dc64b4bc32b66", "shasum": "" }, "require": { @@ -2747,7 +2626,7 @@ ], "support": { "issues": "https://github.com/jfcherng/php-diff/issues", - "source": "https://github.com/jfcherng/php-diff/tree/6.15.2" + "source": "https://github.com/jfcherng/php-diff/tree/6.15.3" }, "funding": [ { @@ -2755,29 +2634,30 @@ "type": "custom" } ], - "time": "2023-06-05T11:42:18+00:00" + "time": "2023-06-15T12:29:57+00:00" }, { "name": "jfcherng/php-mb-string", - "version": "1.4.8", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/jfcherng/php-mb-string.git", - "reference": "ef8926ff849863bfea234e99ee827947bedd86b0" + "reference": "8407bfefde47849c9e7c9594e6de2ac85a0f845d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jfcherng/php-mb-string/zipball/ef8926ff849863bfea234e99ee827947bedd86b0", - "reference": "ef8926ff849863bfea234e99ee827947bedd86b0", + "url": "https://api.github.com/repos/jfcherng/php-mb-string/zipball/8407bfefde47849c9e7c9594e6de2ac85a0f845d", + "reference": "8407bfefde47849c9e7c9594e6de2ac85a0f845d", "shasum": "" }, "require": { - "php": ">=7.1.3" + "ext-iconv": "*", + "php": ">=8.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.18", - "phan/phan": "^2 || ^3 || ^4", - "phpunit/phpunit": "^7.2 || ^8 || ^9" + "friendsofphp/php-cs-fixer": "^3", + "phan/phan": "^5", + "phpunit/phpunit": "^9 || ^10" }, "suggest": { "ext-iconv": "Either \"ext-iconv\" or \"ext-mbstring\" is requried.", @@ -2802,7 +2682,7 @@ "description": "A high performance multibytes sting implementation for frequently reading/writing operations.", "support": { "issues": "https://github.com/jfcherng/php-mb-string/issues", - "source": "https://github.com/jfcherng/php-mb-string/tree/1.4.8" + "source": "https://github.com/jfcherng/php-mb-string/tree/2.0.1" }, "funding": [ { @@ -2810,30 +2690,30 @@ "type": "custom" } ], - "time": "2023-04-17T14:22:37+00:00" + "time": "2023-04-17T14:23:16+00:00" }, { "name": "jfcherng/php-sequence-matcher", - "version": "3.2.10", + "version": "4.0.3", "source": { "type": "git", "url": "https://github.com/jfcherng/php-sequence-matcher.git", - "reference": "650164598be2c6ad6891dbd41de4cb5cc21cc91b" + "reference": "d2038ac29627340a7458609072a8ba355e80ec5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jfcherng/php-sequence-matcher/zipball/650164598be2c6ad6891dbd41de4cb5cc21cc91b", - "reference": "650164598be2c6ad6891dbd41de4cb5cc21cc91b", + "url": "https://api.github.com/repos/jfcherng/php-sequence-matcher/zipball/d2038ac29627340a7458609072a8ba355e80ec5b", + "reference": "d2038ac29627340a7458609072a8ba355e80ec5b", "shasum": "" }, "require": { - "php": ">=7.1.3" + "php": ">=8.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.19", - "phan/phan": "^2.5 || ^3 || ^4 || ^5", - "phpunit/phpunit": ">=7 <10", - "squizlabs/php_codesniffer": "^3.5" + "friendsofphp/php-cs-fixer": "^3", + "phan/phan": "^5", + "phpunit/phpunit": "^9 || ^10", + "squizlabs/php_codesniffer": "^3.7" }, "type": "library", "autoload": { @@ -2858,7 +2738,7 @@ "description": "A longest sequence matcher. The logic is primarily based on the Python difflib package.", "support": { "issues": "https://github.com/jfcherng/php-sequence-matcher/issues", - "source": "https://github.com/jfcherng/php-sequence-matcher/tree/3.2.10" + "source": "https://github.com/jfcherng/php-sequence-matcher/tree/4.0.3" }, "funding": [ { @@ -2866,33 +2746,33 @@ "type": "custom" } ], - "time": "2023-05-21T07:53:38+00:00" + "time": "2023-05-21T07:57:08+00:00" }, { "name": "laminas/laminas-code", - "version": "4.7.1", + "version": "4.11.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-code.git", - "reference": "91aabc066d5620428120800c0eafc0411e441a62" + "reference": "169123b3ede20a9193480c53de2a8194f8c073ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-code/zipball/91aabc066d5620428120800c0eafc0411e441a62", - "reference": "91aabc066d5620428120800c0eafc0411e441a62", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/169123b3ede20a9193480c53de2a8194f8c073ec", + "reference": "169123b3ede20a9193480c53de2a8194f8c073ec", "shasum": "" }, "require": { - "php": ">=7.4, <8.2" + "php": "~8.1.0 || ~8.2.0" }, "require-dev": { - "doctrine/annotations": "^1.13.2", + "doctrine/annotations": "^2.0.0", "ext-phar": "*", "laminas/laminas-coding-standard": "^2.3.0", "laminas/laminas-stdlib": "^3.6.1", - "phpunit/phpunit": "^9.5.10", - "psalm/plugin-phpunit": "^0.17.0", - "vimeo/psalm": "^4.13.1" + "phpunit/phpunit": "^10.0.9", + "psalm/plugin-phpunit": "^0.18.4", + "vimeo/psalm": "^5.7.1" }, "suggest": { "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", @@ -2900,9 +2780,6 @@ }, "type": "library", "autoload": { - "files": [ - "polyfill/ReflectionEnumPolyfill.php" - ], "psr-4": { "Laminas\\Code\\": "src/" } @@ -2932,35 +2809,38 @@ "type": "community_bridge" } ], - "time": "2022-11-21T01:32:31+00:00" + "time": "2023-05-14T12:05:38+00:00" }, { "name": "lcobucci/clock", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/lcobucci/clock.git", - "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3" + "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/clock/zipball/353d83fe2e6ae95745b16b3d911813df6a05bfb3", - "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/039ef98c6b57b101d10bd11d8fdfda12cbd996dc", + "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" + "php": "~8.1.0 || ~8.2.0", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" }, "require-dev": { - "infection/infection": "^0.17", - "lcobucci/coding-standard": "^6.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-deprecation-rules": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/php-code-coverage": "9.1.4", - "phpunit/phpunit": "9.3.7" + "infection/infection": "^0.26", + "lcobucci/coding-standard": "^9.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-deprecation-rules": "^1.1.1", + "phpstan/phpstan-phpunit": "^1.3.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^9.5.27" }, "type": "library", "autoload": { @@ -2981,7 +2861,7 @@ "description": "Yet another clock abstraction", "support": { "issues": "https://github.com/lcobucci/clock/issues", - "source": "https://github.com/lcobucci/clock/tree/2.0.x" + "source": "https://github.com/lcobucci/clock/tree/3.0.0" }, "funding": [ { @@ -2993,43 +2873,44 @@ "type": "patreon" } ], - "time": "2020-08-27T18:56:02+00:00" + "time": "2022-12-19T15:00:24+00:00" }, { "name": "lcobucci/jwt", - "version": "4.3.0", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "4d7de2fe0d51a96418c0d04004986e410e87f6b4" + "reference": "47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/4d7de2fe0d51a96418c0d04004986e410e87f6b4", - "reference": "4d7de2fe0d51a96418c0d04004986e410e87f6b4", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34", + "reference": "47bdb0e0b5d00c2f89ebe33e7e384c77e84e7c34", "shasum": "" }, "require": { "ext-hash": "*", "ext-json": "*", - "ext-mbstring": "*", "ext-openssl": "*", "ext-sodium": "*", - "lcobucci/clock": "^2.0 || ^3.0", - "php": "^7.4 || ^8.0" + "php": "~8.1.0 || ~8.2.0", + "psr/clock": "^1.0" }, "require-dev": { - "infection/infection": "^0.21", - "lcobucci/coding-standard": "^6.0", - "mikey179/vfsstream": "^1.6.7", - "phpbench/phpbench": "^1.2", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/php-invoker": "^3.1", - "phpunit/phpunit": "^9.5" + "infection/infection": "^0.26.19", + "lcobucci/clock": "^3.0", + "lcobucci/coding-standard": "^9.0", + "phpbench/phpbench": "^1.2.8", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.3", + "phpstan/phpstan-deprecation-rules": "^1.1.2", + "phpstan/phpstan-phpunit": "^1.3.8", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^10.0.12" + }, + "suggest": { + "lcobucci/clock": ">= 3.0" }, "type": "library", "autoload": { @@ -3055,7 +2936,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/4.3.0" + "source": "https://github.com/lcobucci/jwt/tree/5.0.0" }, "funding": [ { @@ -3067,7 +2948,7 @@ "type": "patreon" } ], - "time": "2023-01-02T13:28:00+00:00" + "time": "2023-02-25T21:35:16+00:00" }, { "name": "league/csv", @@ -3242,176 +3123,6 @@ ], "time": "2022-03-02T17:24:08+00:00" }, - { - "name": "league/uri", - "version": "6.7.2", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/uri.git", - "reference": "d3b50812dd51f3fbf176344cc2981db03d10fe06" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/d3b50812dd51f3fbf176344cc2981db03d10fe06", - "reference": "d3b50812dd51f3fbf176344cc2981db03d10fe06", - "shasum": "" - }, - "require": { - "ext-json": "*", - "league/uri-interfaces": "^2.3", - "php": "^7.4 || ^8.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "league/uri-schemes": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^v3.3.2", - "nyholm/psr7": "^1.5", - "php-http/psr7-integration-tests": "^1.1", - "phpstan/phpstan": "^1.2.0", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0.0", - "phpstan/phpstan-strict-rules": "^1.1.0", - "phpunit/phpunit": "^9.5.10", - "psr/http-factory": "^1.0" - }, - "suggest": { - "ext-fileinfo": "Needed to create Data URI from a filepath", - "ext-intl": "Needed to improve host validation", - "league/uri-components": "Needed to easily manipulate URI objects", - "psr/http-factory": "Needed to use the URI factory" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.x-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Uri\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ignace Nyamagana Butera", - "email": "nyamsprod@gmail.com", - "homepage": "https://nyamsprod.com" - } - ], - "description": "URI manipulation library", - "homepage": "https://uri.thephpleague.com", - "keywords": [ - "data-uri", - "file-uri", - "ftp", - "hostname", - "http", - "https", - "middleware", - "parse_str", - "parse_url", - "psr-7", - "query-string", - "querystring", - "rfc3986", - "rfc3987", - "rfc6570", - "uri", - "uri-template", - "url", - "ws" - ], - "support": { - "docs": "https://uri.thephpleague.com", - "forum": "https://thephpleague.slack.com", - "issues": "https://github.com/thephpleague/uri/issues", - "source": "https://github.com/thephpleague/uri/tree/6.7.2" - }, - "funding": [ - { - "url": "https://github.com/sponsors/nyamsprod", - "type": "github" - } - ], - "time": "2022-09-13T19:50:42+00:00" - }, - { - "name": "league/uri-interfaces", - "version": "2.3.0", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/00e7e2943f76d8cb50c7dfdc2f6dee356e15e383", - "reference": "00e7e2943f76d8cb50c7dfdc2f6dee356e15e383", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.19", - "phpstan/phpstan": "^0.12.90", - "phpstan/phpstan-phpunit": "^0.12.19", - "phpstan/phpstan-strict-rules": "^0.12.9", - "phpunit/phpunit": "^8.5.15 || ^9.5" - }, - "suggest": { - "ext-intl": "to use the IDNA feature", - "symfony/intl": "to use the IDNA feature via Symfony Polyfill" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Uri\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ignace Nyamagana Butera", - "email": "nyamsprod@gmail.com", - "homepage": "https://nyamsprod.com" - } - ], - "description": "Common interface for URI representation", - "homepage": "http://github.com/thephpleague/uri-interfaces", - "keywords": [ - "rfc3986", - "rfc3987", - "uri", - "url" - ], - "support": { - "issues": "https://github.com/thephpleague/uri-interfaces/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/2.3.0" - }, - "funding": [ - { - "url": "https://github.com/sponsors/nyamsprod", - "type": "github" - } - ], - "time": "2021-06-28T04:27:21+00:00" - }, { "name": "liip/imagine-bundle", "version": "2.11.0", @@ -3637,42 +3348,41 @@ }, { "name": "monolog/monolog", - "version": "2.9.1", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1" + "reference": "e2392369686d420ca32df3803de28b5d6f76867d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f259e2b15fb95494c83f52d3caad003bbf5ffaa1", - "reference": "f259e2b15fb95494c83f52d3caad003bbf5ffaa1", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/e2392369686d420ca32df3803de28b5d6f76867d", + "reference": "e2392369686d420ca32df3803de28b5d6f76867d", "shasum": "" }, "require": { - "php": ">=7.2", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + "psr/log-implementation": "3.0.0" }, "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "aws/aws-sdk-php": "^3.0", "doctrine/couchdb": "~1.0@dev", "elasticsearch/elasticsearch": "^7 || ^8", "ext-json": "*", - "graylog2/gelf-php": "^1.4.2 || ^2@dev", - "guzzlehttp/guzzle": "^7.4", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpspec/prophecy": "^1.15", - "phpstan/phpstan": "^0.12.91", - "phpunit/phpunit": "^8.5.14", - "predis/predis": "^1.1 || ^2.0", - "rollbar/rollbar": "^1.3 || ^2 || ^3", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^10.1", + "predis/predis": "^1.1 || ^2", "ruflin/elastica": "^7", - "swiftmailer/swiftmailer": "^5.3|^6.0", "symfony/mailer": "^5.4 || ^6", "symfony/mime": "^5.4 || ^6" }, @@ -3695,7 +3405,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -3723,7 +3433,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.9.1" + "source": "https://github.com/Seldaek/monolog/tree/3.4.0" }, "funding": [ { @@ -3735,7 +3445,74 @@ "type": "tidelift" } ], - "time": "2023-02-06T13:44:46+00:00" + "time": "2023-06-21T08:46:11+00:00" + }, + { + "name": "nbgrp/onelogin-saml-bundle", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/nbgrp/onelogin-saml-bundle.git", + "reference": "907a59431edcfbb962b2bb952d987693b63ca757" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nbgrp/onelogin-saml-bundle/zipball/907a59431edcfbb962b2bb952d987693b63ca757", + "reference": "907a59431edcfbb962b2bb952d987693b63ca757", + "shasum": "" + }, + "require": { + "onelogin/php-saml": "^4", + "php": "^8.1", + "psr/log": "^1 || ^2 || ^3", + "symfony/config": "^6", + "symfony/dependency-injection": "^6", + "symfony/deprecation-contracts": "^3", + "symfony/event-dispatcher-contracts": "^3", + "symfony/http-foundation": "^6", + "symfony/http-kernel": "^6", + "symfony/routing": "^6", + "symfony/security-bundle": "^6", + "symfony/security-core": "^6", + "symfony/security-http": "^6" + }, + "conflict": { + "symfony/http-kernel": "<6.2", + "symfony/security-core": "<6.2" + }, + "require-dev": { + "doctrine/orm": "^2.3 || ^3", + "symfony/event-dispatcher": "^6", + "symfony/phpunit-bridge": "^6" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Nbgrp\\OneloginSamlBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Alexander Menshchikov", + "email": "alexander.menshchikov@yandex.ru" + } + ], + "description": "OneLogin SAML Symfony Bundle", + "keywords": [ + "SSO", + "multiple IdP", + "onelogin", + "saml" + ], + "support": { + "issues": "https://github.com/nbgrp/onelogin-saml-bundle/issues", + "source": "https://github.com/nbgrp/onelogin-saml-bundle/tree/v1.3.2" + }, + "time": "2023-03-22T20:23:42+00:00" }, { "name": "nelexa/zip", @@ -3885,16 +3662,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.5", + "version": "v4.16.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e" + "reference": "19526a33fb561ef417e822e85f08a00db4059c17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e", - "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17", + "reference": "19526a33fb561ef417e822e85f08a00db4059c17", "shasum": "" }, "require": { @@ -3935,9 +3712,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0" }, - "time": "2023-05-19T20:20:00+00:00" + "time": "2023-06-25T14:52:30+00:00" }, { "name": "nikolaposa/version", @@ -4137,49 +3914,54 @@ }, { "name": "omines/datatables-bundle", - "version": "0.5.5", + "version": "0.7.2", "source": { "type": "git", "url": "https://github.com/omines/datatables-bundle.git", - "reference": "b8a16c365f9d8e97d1e890e8783249f979f0d7ca" + "reference": "8e0dce49a271e0cfdf128d42bf81a336f8f02232" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/omines/datatables-bundle/zipball/b8a16c365f9d8e97d1e890e8783249f979f0d7ca", - "reference": "b8a16c365f9d8e97d1e890e8783249f979f0d7ca", + "url": "https://api.github.com/repos/omines/datatables-bundle/zipball/8e0dce49a271e0cfdf128d42bf81a336f8f02232", + "reference": "8e0dce49a271e0cfdf128d42bf81a336f8f02232", "shasum": "" }, "require": { - "php": ">=7.2", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/framework-bundle": "^4.4|^5.0", - "symfony/options-resolver": "^4.4|^5.0", - "symfony/property-access": "^4.4|^5.0", - "symfony/translation": "^4.4|^5.0" + "php": ">=8.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/options-resolver": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0" }, "require-dev": { - "doctrine/common": "^2.6|^3.0", - "doctrine/doctrine-bundle": "^2.3|^3.0", - "doctrine/orm": "^2.6.3", - "doctrine/persistence": "^1.3.4|^2.0", + "doctrine/common": "^2.6|^3.3", + "doctrine/doctrine-bundle": "^2.7|^3.0", + "doctrine/orm": "^2.13.1", + "doctrine/persistence": "^2.0|^3.0.3", "ext-curl": "*", "ext-json": "*", "ext-pdo_sqlite": "*", "ext-zip": "*", - "friendsofphp/php-cs-fixer": "^2.7", - "mongodb/mongodb": "^1.2", - "ocramius/package-versions": "^1.4", - "phpoffice/phpspreadsheet": "^1.6", - "ruflin/elastica": "^6.0", - "symfony/browser-kit": "^4.4|^5.0", - "symfony/css-selector": "^4.4|^5.0", - "symfony/dom-crawler": "^4.4|^5.0", - "symfony/intl": "^4.4|^5.0", - "symfony/mime": "^4.4|^5.0", - "symfony/phpunit-bridge": "^4.4|^5.0", - "symfony/twig-bundle": "^4.4|^5.0", - "symfony/var-dumper": "^4.4|^5.0", - "symfony/yaml": "^4.4|^5.0" + "friendsofphp/php-cs-fixer": "^3.9.5", + "mongodb/mongodb": "^1.12", + "ocramius/package-versions": "^2.5", + "phpoffice/phpspreadsheet": "^1.24.1", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8.2", + "phpstan/phpstan-doctrine": "^1.3.12", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-symfony": "^1.2.9", + "ruflin/elastica": "^6.0|^7.2", + "symfony/browser-kit": "^5.4|^6.1.3", + "symfony/css-selector": "^5.4|^6.1.3", + "symfony/dom-crawler": "^5.4|^6.1.3", + "symfony/intl": "^5.4|^6.1", + "symfony/mime": "^5.4|^6.1.3", + "symfony/phpunit-bridge": "^5.4|^6.1.3", + "symfony/twig-bundle": "^5.4|^6.1.1", + "symfony/var-dumper": "^5.4|^6.1.3", + "symfony/yaml": "^5.4|^6.1.3" }, "suggest": { "doctrine/doctrine-bundle": "For integrated access to Doctrine object managers", @@ -4192,7 +3974,7 @@ "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "0.5-dev" + "dev-master": "0.7-dev" } }, "autoload": { @@ -4230,40 +4012,41 @@ ], "support": { "issues": "https://github.com/omines/datatables-bundle/issues", - "source": "https://github.com/omines/datatables-bundle/tree/0.5.5" + "source": "https://github.com/omines/datatables-bundle/tree/0.7.2" }, - "time": "2021-06-29T08:12:37+00:00" + "time": "2023-04-24T09:09:02+00:00" }, { "name": "onelogin/php-saml", - "version": "3.6.1", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/onelogin/php-saml.git", - "reference": "a7328b11887660ad248ea10952dd67a5aa73ba3b" + "reference": "b22a57ebd13e838b90df5d3346090bc37056409d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/onelogin/php-saml/zipball/a7328b11887660ad248ea10952dd67a5aa73ba3b", - "reference": "a7328b11887660ad248ea10952dd67a5aa73ba3b", + "url": "https://api.github.com/repos/onelogin/php-saml/zipball/b22a57ebd13e838b90df5d3346090bc37056409d", + "reference": "b22a57ebd13e838b90df5d3346090bc37056409d", "shasum": "" }, "require": { - "php": ">=5.4", + "php": ">=7.3", "robrichards/xmlseclibs": ">=3.1.1" }, "require-dev": { - "pdepend/pdepend": "^2.5.0", - "php-coveralls/php-coveralls": "^1.0.2 || ^2.0", - "phploc/phploc": "^2.1 || ^3.0 || ^4.0", - "phpunit/phpunit": "<7.5.18", - "sebastian/phpcpd": "^2.0 || ^3.0 || ^4.0", - "squizlabs/php_codesniffer": "^3.1.1" + "pdepend/pdepend": "^2.8.0", + "php-coveralls/php-coveralls": "^2.0", + "phploc/phploc": "^4.0 || ^5.0 || ^6.0 || ^7.0", + "phpunit/phpunit": "^9.5", + "sebastian/phpcpd": "^4.0 || ^5.0 || ^6.0 ", + "squizlabs/php_codesniffer": "^3.5.8" }, "suggest": { "ext-curl": "Install curl lib to be able to use the IdPMetadataParser for parsing remote XMLs", - "ext-gettext": "Install gettext and php5-gettext libs to handle translations", - "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)" + "ext-dom": "Install xml lib", + "ext-openssl": "Install openssl lib in order to handle with x509 certs (require to support sign and encryption)", + "ext-zlib": "Install zlib" }, "type": "library", "autoload": { @@ -4287,7 +4070,7 @@ "issues": "https://github.com/onelogin/php-saml/issues", "source": "https://github.com/onelogin/php-saml/" }, - "time": "2021-03-02T10:13:07+00:00" + "time": "2022-07-15T20:44:36+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -4448,16 +4231,16 @@ }, { "name": "php-http/discovery", - "version": "1.18.1", + "version": "1.19.0", "source": { "type": "git", "url": "https://github.com/php-http/discovery.git", - "reference": "f258b3a1d16acb7b21f3b42d7a2494a733365237" + "reference": "1856a119a0b0ba8da8b5c33c080aa7af8fac25b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/discovery/zipball/f258b3a1d16acb7b21f3b42d7a2494a733365237", - "reference": "f258b3a1d16acb7b21f3b42d7a2494a733365237", + "url": "https://api.github.com/repos/php-http/discovery/zipball/1856a119a0b0ba8da8b5c33c080aa7af8fac25b4", + "reference": "1856a119a0b0ba8da8b5c33c080aa7af8fac25b4", "shasum": "" }, "require": { @@ -4520,9 +4303,9 @@ ], "support": { "issues": "https://github.com/php-http/discovery/issues", - "source": "https://github.com/php-http/discovery/tree/1.18.1" + "source": "https://github.com/php-http/discovery/tree/1.19.0" }, - "time": "2023-05-17T08:53:10+00:00" + "time": "2023-06-19T08:45:36+00:00" }, { "name": "php-http/httplug", @@ -5139,20 +4922,20 @@ }, { "name": "psr/cache", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { @@ -5172,7 +4955,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for caching libraries", @@ -5182,28 +4965,81 @@ "psr-6" ], "support": { - "source": "https://github.com/php-fig/cache/tree/master" + "source": "https://github.com/php-fig/cache/tree/3.0.0" }, - "time": "2016-08-06T20:24:11+00:00" + "time": "2021-02-03T23:26:27+00:00" }, { - "name": "psr/container", - "version": "1.1.2", + "name": "psr/clock", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", - "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { "php": ">=7.4.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -5230,9 +5066,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.2" + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "time": "2021-11-05T16:50:12+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { "name": "psr/event-dispatcher", @@ -5393,16 +5229,16 @@ }, { "name": "psr/http-message", - "version": "1.1", + "version": "2.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", - "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { @@ -5411,7 +5247,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -5426,7 +5262,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -5440,31 +5276,34 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/1.1" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "time": "2023-04-04T09:50:52+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { "name": "psr/link", - "version": "1.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/php-fig/link.git", - "reference": "eea8e8662d5cd3ae4517c9b864493f59fca95562" + "reference": "84b159194ecfd7eaa472280213976e96415433f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/link/zipball/eea8e8662d5cd3ae4517c9b864493f59fca95562", - "reference": "eea8e8662d5cd3ae4517c9b864493f59fca95562", + "url": "https://api.github.com/repos/php-fig/link/zipball/84b159194ecfd7eaa472280213976e96415433f7", + "reference": "84b159194ecfd7eaa472280213976e96415433f7", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" + }, + "suggest": { + "fig/link-util": "Provides some useful PSR-13 utilities" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -5483,6 +5322,7 @@ } ], "description": "Common interfaces for HTTP links", + "homepage": "https://github.com/php-fig/link", "keywords": [ "http", "http-link", @@ -5492,36 +5332,36 @@ "rest" ], "support": { - "source": "https://github.com/php-fig/link/tree/master" + "source": "https://github.com/php-fig/link/tree/2.0.1" }, - "time": "2016-10-28T16:06:13+00:00" + "time": "2021-03-11T23:00:27+00:00" }, { "name": "psr/log", - "version": "1.1.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", - "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -5542,31 +5382,31 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.4" + "source": "https://github.com/php-fig/log/tree/3.0.0" }, - "time": "2021-05-03T11:20:27+00:00" + "time": "2021-07-14T16:46:02+00:00" }, { "name": "psr/simple-cache", - "version": "1.0.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-fig/simple-cache.git", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { @@ -5581,7 +5421,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for simple caching", @@ -5593,197 +5433,9 @@ "simple-cache" ], "support": { - "source": "https://github.com/php-fig/simple-cache/tree/master" + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" }, - "time": "2017-10-23T01:57:42+00:00" - }, - { - "name": "ramsey/collection", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/ramsey/collection.git", - "reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/ad7475d1c9e70b190ecffc58f2d989416af339b4", - "reference": "ad7475d1c9e70b190ecffc58f2d989416af339b4", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0", - "symfony/polyfill-php81": "^1.23" - }, - "require-dev": { - "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.28.3", - "fakerphp/faker": "^1.21", - "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^1.0", - "mockery/mockery": "^1.5", - "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpcsstandards/phpcsutils": "^1.0.0-rc1", - "phpspec/prophecy-phpunit": "^2.0", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5", - "psalm/plugin-mockery": "^1.1", - "psalm/plugin-phpunit": "^0.18.4", - "ramsey/coding-standard": "^2.0.3", - "ramsey/conventional-commits": "^1.3", - "vimeo/psalm": "^5.4" - }, - "type": "library", - "extra": { - "captainhook": { - "force-install": true - }, - "ramsey/conventional-commits": { - "configFile": "conventional-commits.json" - } - }, - "autoload": { - "psr-4": { - "Ramsey\\Collection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "A PHP library for representing and manipulating collections.", - "keywords": [ - "array", - "collection", - "hash", - "map", - "queue", - "set" - ], - "support": { - "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/1.3.0" - }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", - "type": "tidelift" - } - ], - "time": "2022-12-27T19:12:24+00:00" - }, - { - "name": "ramsey/uuid", - "version": "4.2.3", - "source": { - "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", - "reference": "fc9bb7fb5388691fd7373cd44dcb4d63bbcf24df", - "shasum": "" - }, - "require": { - "brick/math": "^0.8 || ^0.9", - "ext-json": "*", - "php": "^7.2 || ^8.0", - "ramsey/collection": "^1.0", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php80": "^1.14" - }, - "replace": { - "rhumsaa/uuid": "self.version" - }, - "require-dev": { - "captainhook/captainhook": "^5.10", - "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.8", - "ergebnis/composer-normalize": "^2.15", - "mockery/mockery": "^1.3", - "moontoast/math": "^1.1", - "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.2", - "php-mock/php-mock-mockery": "^1.3", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpbench/phpbench": "^1.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-mockery": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^8.5 || ^9", - "slevomat/coding-standard": "^7.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "^4.9" - }, - "suggest": { - "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-ctype": "Enables faster processing of character classification using ctype functions.", - "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", - "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "4.x-dev" - }, - "captainhook": { - "force-install": true - } - }, - "autoload": { - "files": [ - "src/functions.php" - ], - "psr-4": { - "Ramsey\\Uuid\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", - "keywords": [ - "guid", - "identifier", - "uuid" - ], - "support": { - "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.2.3" - }, - "funding": [ - { - "url": "https://github.com/ramsey", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", - "type": "tidelift" - } - ], - "time": "2021-09-25T23:10:38+00:00" + "time": "2021-10-29T13:26:27+00:00" }, { "name": "robrichards/xmlseclibs", @@ -5871,24 +5523,24 @@ }, { "name": "s9e/sweetdom", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/s9e/SweetDOM.git", - "reference": "9e34ff8f353234daed102274012c840bda56aff2" + "reference": "dd5d814f93621b1489bfbac8e0331122b928a18a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/SweetDOM/zipball/9e34ff8f353234daed102274012c840bda56aff2", - "reference": "9e34ff8f353234daed102274012c840bda56aff2", + "url": "https://api.github.com/repos/s9e/SweetDOM/zipball/dd5d814f93621b1489bfbac8e0331122b928a18a", + "reference": "dd5d814f93621b1489bfbac8e0331122b928a18a", "shasum": "" }, "require": { "ext-dom": "*", - "php": ">=7.1" + "php": ">=8.0" }, "require-dev": { - "phpunit/phpunit": "*" + "phpunit/phpunit": "^10.0" }, "type": "library", "autoload": { @@ -5909,29 +5561,29 @@ ], "support": { "issues": "https://github.com/s9e/SweetDOM/issues", - "source": "https://github.com/s9e/SweetDOM/tree/2.1.0" + "source": "https://github.com/s9e/SweetDOM/tree/2.1.1" }, - "time": "2021-05-24T21:06:33+00:00" + "time": "2023-06-05T19:10:26+00:00" }, { "name": "s9e/text-formatter", - "version": "2.13.1", + "version": "2.14.0", "source": { "type": "git", "url": "https://github.com/s9e/TextFormatter.git", - "reference": "bbd9e34e9c30d5daeb780f115fe69cd81dd9c352" + "reference": "48a2f3a3fb18af8d78330204732a3369441c4060" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/bbd9e34e9c30d5daeb780f115fe69cd81dd9c352", - "reference": "bbd9e34e9c30d5daeb780f115fe69cd81dd9c352", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/48a2f3a3fb18af8d78330204732a3369441c4060", + "reference": "48a2f3a3fb18af8d78330204732a3369441c4060", "shasum": "" }, "require": { "ext-dom": "*", "ext-filter": "*", "lib-pcre": ">=8.13", - "php": ">=7.4", + "php": "^8.0", "s9e/regexp-builder": "^1.4", "s9e/sweetdom": "^2.0" }, @@ -5951,7 +5603,7 @@ }, "type": "library", "extra": { - "version": "2.13.1" + "version": "2.14.0" }, "autoload": { "psr-4": { @@ -5983,9 +5635,9 @@ ], "support": { "issues": "https://github.com/s9e/TextFormatter/issues", - "source": "https://github.com/s9e/TextFormatter/tree/2.13.1" + "source": "https://github.com/s9e/TextFormatter/tree/2.14.0" }, - "time": "2023-02-11T00:18:05+00:00" + "time": "2023-06-08T07:19:50+00:00" }, { "name": "sabberworm/php-css-parser", @@ -6042,19 +5694,20 @@ }, { "name": "scheb/2fa-backup-code", - "version": "v5.13.2", + "version": "v6.8.0", "source": { "type": "git", "url": "https://github.com/scheb/2fa-backup-code.git", - "reference": "5584eb7a2c3deb80635c7173ad77858e51129c35" + "reference": "b01965cd221cda280526e48e7f56966154b9ba2f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-backup-code/zipball/5584eb7a2c3deb80635c7173ad77858e51129c35", - "reference": "5584eb7a2c3deb80635c7173ad77858e51129c35", + "url": "https://api.github.com/repos/scheb/2fa-backup-code/zipball/b01965cd221cda280526e48e7f56966154b9ba2f", + "reference": "b01965cd221cda280526e48e7f56966154b9ba2f", "shasum": "" }, "require": { + "php": "~8.0.0 || ~8.1.0 || ~8.2.0", "scheb/2fa-bundle": "self.version" }, "type": "library", @@ -6084,36 +5737,36 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-backup-code/tree/v5.13.2" + "source": "https://github.com/scheb/2fa-backup-code/tree/v6.8.0" }, - "time": "2022-01-03T10:21:24+00:00" + "time": "2022-12-10T15:20:09+00:00" }, { "name": "scheb/2fa-bundle", - "version": "v5.13.2", + "version": "v6.8.0", "source": { "type": "git", "url": "https://github.com/scheb/2fa-bundle.git", - "reference": "dc575cc7bc94fa3a52b547698086f2ef015d2e81" + "reference": "4f8e9e87f90cf50c72b0857ea2b88453cf1d2446" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-bundle/zipball/dc575cc7bc94fa3a52b547698086f2ef015d2e81", - "reference": "dc575cc7bc94fa3a52b547698086f2ef015d2e81", + "url": "https://api.github.com/repos/scheb/2fa-bundle/zipball/4f8e9e87f90cf50c72b0857ea2b88453cf1d2446", + "reference": "4f8e9e87f90cf50c72b0857ea2b88453cf1d2446", "shasum": "" }, "require": { "ext-json": "*", - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/framework-bundle": "^4.4|^5.0", - "symfony/http-foundation": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0", - "symfony/property-access": "^4.4|^5.0", - "symfony/security-bundle": "^4.4.1|^5.0", - "symfony/twig-bundle": "^4.4|^5.0" + "php": "~8.0.0 || ~8.1.0 || ~8.2.0", + "symfony/config": "^5.4 || ^6.0", + "symfony/dependency-injection": "^5.4 || ^6.0", + "symfony/event-dispatcher": "^5.4 || ^6.0", + "symfony/framework-bundle": "^5.4 || ^6.0", + "symfony/http-foundation": "^5.4 || ^6.0", + "symfony/http-kernel": "^5.4 || ^6.0", + "symfony/property-access": "^5.4 || ^6.0", + "symfony/security-bundle": "^5.4 || ^6.0", + "symfony/twig-bundle": "^5.4 || ^6.0" }, "conflict": { "scheb/two-factor-bundle": "*" @@ -6122,7 +5775,6 @@ "scheb/2fa-backup-code": "Emergency codes when you have no access to other methods", "scheb/2fa-email": "Send codes by email", "scheb/2fa-google-authenticator": "Google Authenticator support", - "scheb/2fa-qr-code": "Generate QR codes for Google Authenticator / TOTP", "scheb/2fa-totp": "Temporary one-time password (TOTP) support (Google Authenticator compatible)", "scheb/2fa-trusted-device": "Trusted devices support" }, @@ -6152,28 +5804,29 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-bundle/tree/v5.13.2" + "source": "https://github.com/scheb/2fa-bundle/tree/v6.8.0" }, - "time": "2022-04-16T10:18:34+00:00" + "time": "2023-01-26T18:47:22+00:00" }, { "name": "scheb/2fa-google-authenticator", - "version": "v5.13.2", + "version": "v6.8.0", "source": { "type": "git", "url": "https://github.com/scheb/2fa-google-authenticator.git", - "reference": "9477bfc47b5927fb165022dd75700aefdd45a8cc" + "reference": "20eab4c1814b587cac71c4516a06b192ca838294" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-google-authenticator/zipball/9477bfc47b5927fb165022dd75700aefdd45a8cc", - "reference": "9477bfc47b5927fb165022dd75700aefdd45a8cc", + "url": "https://api.github.com/repos/scheb/2fa-google-authenticator/zipball/20eab4c1814b587cac71c4516a06b192ca838294", + "reference": "20eab4c1814b587cac71c4516a06b192ca838294", "shasum": "" }, "require": { - "paragonie/constant_time_encoding": "^2.2", + "paragonie/constant_time_encoding": "^2.4", + "php": "~8.0.0 || ~8.1.0 || ~8.2.0", "scheb/2fa-bundle": "self.version", - "spomky-labs/otphp": "^9.1|^10.0" + "spomky-labs/otphp": "^10.0 || ^11.0" }, "type": "library", "autoload": { @@ -6202,26 +5855,28 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-google-authenticator/tree/v5.13.2" + "source": "https://github.com/scheb/2fa-google-authenticator/tree/v6.8.0" }, - "time": "2022-01-03T10:21:24+00:00" + "time": "2022-12-10T15:20:09+00:00" }, { "name": "scheb/2fa-trusted-device", - "version": "v5.13.2", + "version": "v6.8.0", "source": { "type": "git", "url": "https://github.com/scheb/2fa-trusted-device.git", - "reference": "acf5a1526eb2111fb7a82b9b52eb34b1ddfdc526" + "reference": "cac6feaf9f2c7d3a1aade86942f7b7b234fcd151" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/scheb/2fa-trusted-device/zipball/acf5a1526eb2111fb7a82b9b52eb34b1ddfdc526", - "reference": "acf5a1526eb2111fb7a82b9b52eb34b1ddfdc526", + "url": "https://api.github.com/repos/scheb/2fa-trusted-device/zipball/cac6feaf9f2c7d3a1aade86942f7b7b234fcd151", + "reference": "cac6feaf9f2c7d3a1aade86942f7b7b234fcd151", "shasum": "" }, "require": { - "lcobucci/jwt": "^3.4|^4.0", + "lcobucci/clock": "^2.0 || ^3.0", + "lcobucci/jwt": "^4.1 || ^5.0", + "php": "~8.0.0 || ~8.1.0 || ~8.2.0", "scheb/2fa-bundle": "self.version" }, "type": "library", @@ -6251,87 +5906,9 @@ "two-step" ], "support": { - "source": "https://github.com/scheb/2fa-trusted-device/tree/v5.13.2" + "source": "https://github.com/scheb/2fa-trusted-device/tree/v6.8.0" }, - "time": "2022-01-03T10:21:24+00:00" - }, - { - "name": "sensio/framework-extra-bundle", - "version": "v6.2.10", - "source": { - "type": "git", - "url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git", - "reference": "2f886f4b31f23c76496901acaedfedb6936ba61f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/2f886f4b31f23c76496901acaedfedb6936ba61f", - "reference": "2f886f4b31f23c76496901acaedfedb6936ba61f", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^1.0|^2.0", - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/framework-bundle": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0" - }, - "conflict": { - "doctrine/doctrine-cache-bundle": "<1.3.1", - "doctrine/persistence": "<1.3" - }, - "require-dev": { - "doctrine/dbal": "^2.10|^3.0", - "doctrine/doctrine-bundle": "^1.11|^2.0", - "doctrine/orm": "^2.5", - "symfony/browser-kit": "^4.4|^5.0|^6.0", - "symfony/doctrine-bridge": "^4.4|^5.0|^6.0", - "symfony/dom-crawler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/monolog-bridge": "^4.0|^5.0|^6.0", - "symfony/monolog-bundle": "^3.2", - "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0", - "symfony/security-bundle": "^4.4|^5.0|^6.0", - "symfony/twig-bundle": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0", - "twig/twig": "^1.34|^2.4|^3.0" - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "6.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Sensio\\Bundle\\FrameworkExtraBundle\\": "src/" - }, - "exclude-from-classmap": [ - "/tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "This bundle provides a way to configure your controllers with annotations", - "keywords": [ - "annotations", - "controllers" - ], - "support": { - "source": "https://github.com/sensiolabs/SensioFrameworkExtraBundle/tree/v6.2.10" - }, - "abandoned": "Symfony", - "time": "2023-02-24T14:57:12+00:00" + "time": "2023-04-01T11:20:00+00:00" }, { "name": "shivas/versioning-bundle", @@ -6395,24 +5972,24 @@ }, { "name": "spatie/db-dumper", - "version": "2.21.1", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/spatie/db-dumper.git", - "reference": "05e5955fb882008a8947c5a45146d86cfafa10d1" + "reference": "3b9fd47899bf6a59d3452392121c9ce675d55d34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/db-dumper/zipball/05e5955fb882008a8947c5a45146d86cfafa10d1", - "reference": "05e5955fb882008a8947c5a45146d86cfafa10d1", + "url": "https://api.github.com/repos/spatie/db-dumper/zipball/3b9fd47899bf6a59d3452392121c9ce675d55d34", + "reference": "3b9fd47899bf6a59d3452392121c9ce675d55d34", "shasum": "" }, "require": { - "php": "^7.2|^8.0", - "symfony/process": "^4.2|^5.0" + "php": "^8.0", + "symfony/process": "^5.0|^6.0" }, "require-dev": { - "phpunit/phpunit": "^7.0|^8.0|^9.0" + "pestphp/pest": "^1.22" }, "type": "library", "autoload": { @@ -6442,114 +6019,54 @@ "spatie" ], "support": { - "issues": "https://github.com/spatie/db-dumper/issues", - "source": "https://github.com/spatie/db-dumper/tree/2.21.1" + "source": "https://github.com/spatie/db-dumper/tree/3.3.1" }, "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, { "url": "https://github.com/spatie", "type": "github" } ], - "time": "2021-02-24T14:56:42+00:00" - }, - { - "name": "spomky-labs/base64url", - "version": "v2.0.4", - "source": { - "type": "git", - "url": "https://github.com/Spomky-Labs/base64url.git", - "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/7752ce931ec285da4ed1f4c5aa27e45e097be61d", - "reference": "7752ce931ec285da4ed1f4c5aa27e45e097be61d", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "require-dev": { - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.11|^0.12", - "phpstan/phpstan-beberlei-assert": "^0.11|^0.12", - "phpstan/phpstan-deprecation-rules": "^0.11|^0.12", - "phpstan/phpstan-phpunit": "^0.11|^0.12", - "phpstan/phpstan-strict-rules": "^0.11|^0.12" - }, - "type": "library", - "autoload": { - "psr-4": { - "Base64Url\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Florent Morselli", - "homepage": "https://github.com/Spomky-Labs/base64url/contributors" - } - ], - "description": "Base 64 URL Safe Encoding/Decoding PHP Library", - "homepage": "https://github.com/Spomky-Labs/base64url", - "keywords": [ - "base64", - "rfc4648", - "safe", - "url" - ], - "support": { - "issues": "https://github.com/Spomky-Labs/base64url/issues", - "source": "https://github.com/Spomky-Labs/base64url/tree/v2.0.4" - }, - "funding": [ - { - "url": "https://github.com/Spomky", - "type": "github" - }, - { - "url": "https://www.patreon.com/FlorentMorselli", - "type": "patreon" - } - ], - "time": "2020-11-03T09:10:25+00:00" + "time": "2023-05-02T11:05:31+00:00" }, { "name": "spomky-labs/cbor-bundle", - "version": "v2.0.3", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/cbor-bundle.git", - "reference": "65a5a65e7fc20eca383a0be8f3ed287a4fe80b1f" + "reference": "157ca6ed2f6e957f9e95d71ca86bc67bf42ee79c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/cbor-bundle/zipball/65a5a65e7fc20eca383a0be8f3ed287a4fe80b1f", - "reference": "65a5a65e7fc20eca383a0be8f3ed287a4fe80b1f", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-bundle/zipball/157ca6ed2f6e957f9e95d71ca86bc67bf42ee79c", + "reference": "157ca6ed2f6e957f9e95d71ca86bc67bf42ee79c", "shasum": "" }, "require": { - "php": ">=7.1", - "spomky-labs/cbor-php": "^1.0|^2.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/http-kernel": "^4.4|^5.0" + "php": ">=8.0", + "spomky-labs/cbor-php": "^3.0", + "symfony/config": "^5.3|^6.0", + "symfony/dependency-injection": "^5.3|^6.0", + "symfony/http-kernel": "^5.3|^6.0" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-beberlei-assert": "^0.12", - "phpstan/phpstan-deprecation-rules": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", + "infection/infection": "^0.25.3", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-beberlei-assert": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.0", - "symfony/framework-bundle": "^4.4|^5.0", - "symfony/phpunit-bridge": "^4.4|^5.0", - "thecodingmachine/phpstan-safe-rule": "^1.0@beta" + "rector/rector": "^0.12.5", + "symfony/framework-bundle": "^5.3|^6.0", + "symfony/phpunit-bridge": "^5.3|^6.0", + "symplify/easy-coding-standard": "^9.4" }, "type": "symfony-bundle", "autoload": { @@ -6582,43 +6099,56 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/cbor-bundle/issues", - "source": "https://github.com/Spomky-Labs/cbor-bundle/tree/v2.0.3" + "source": "https://github.com/Spomky-Labs/cbor-bundle/tree/v3.0.0" }, - "time": "2020-07-12T22:47:45+00:00" + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2021-11-23T21:41:00+00:00" }, { "name": "spomky-labs/cbor-php", - "version": "v2.1.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/cbor-php.git", - "reference": "28e2712cfc0b48fae661a48ffc6896d7abe83684" + "reference": "81d5dff7a1101d680729b5789f4359d01b15e6c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/28e2712cfc0b48fae661a48ffc6896d7abe83684", - "reference": "28e2712cfc0b48fae661a48ffc6896d7abe83684", + "url": "https://api.github.com/repos/Spomky-Labs/cbor-php/zipball/81d5dff7a1101d680729b5789f4359d01b15e6c5", + "reference": "81d5dff7a1101d680729b5789f4359d01b15e6c5", "shasum": "" }, "require": { - "brick/math": "^0.8.15|^0.9.0", + "brick/math": "^0.9|^0.10|^0.11", "ext-mbstring": "*", - "php": ">=7.3" + "php": ">=8.0" }, "require-dev": { "ekino/phpstan-banned-code": "^1.0", "ext-json": "*", - "infection/infection": "^0.18|^0.25", + "infection/infection": "^0.26", + "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/extension-installer": "^1.1", "phpstan/phpstan": "^1.0", "phpstan/phpstan-beberlei-assert": "^1.0", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", - "rector/rector": "^0.12", + "phpunit/phpunit": "^10.0", + "qossmic/deptrac-shim": "^1.0", + "rector/rector": "^0.15", "roave/security-advisories": "dev-latest", - "symplify/easy-coding-standard": "^10.0" + "symfony/var-dumper": "^6.0", + "symplify/easy-coding-standard": "^11.1" }, "suggest": { "ext-bcmath": "GMP or BCMath extensions will drastically improve the library performance. BCMath extension needed to handle the Big Float and Decimal Fraction Tags", @@ -6652,7 +6182,7 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/cbor-php/issues", - "source": "https://github.com/Spomky-Labs/cbor-php/tree/v2.1.0" + "source": "https://github.com/Spomky-Labs/cbor-php/tree/3.0.2" }, "funding": [ { @@ -6664,47 +6194,42 @@ "type": "patreon" } ], - "time": "2021-12-13T12:46:26+00:00" + "time": "2023-02-28T21:37:12+00:00" }, { "name": "spomky-labs/otphp", - "version": "v10.0.3", + "version": "11.2.0", "source": { "type": "git", "url": "https://github.com/Spomky-Labs/otphp.git", - "reference": "9784d9f7c790eed26e102d6c78f12c754036c366" + "reference": "9a1569038bb1c8e98040b14b8bcbba54f25e7795" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/9784d9f7c790eed26e102d6c78f12c754036c366", - "reference": "9784d9f7c790eed26e102d6c78f12c754036c366", + "url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/9a1569038bb1c8e98040b14b8bcbba54f25e7795", + "reference": "9a1569038bb1c8e98040b14b8bcbba54f25e7795", "shasum": "" }, "require": { - "beberlei/assert": "^3.0", "ext-mbstring": "*", "paragonie/constant_time_encoding": "^2.0", - "php": "^7.2|^8.0", - "thecodingmachine/safe": "^0.1.14|^1.0|^2.0" + "php": "^8.1" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-beberlei-assert": "^0.12", - "phpstan/phpstan-deprecation-rules": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/phpunit": "^8.0", - "thecodingmachine/phpstan-safe-rule": "^1.0 || ^2.0" + "ekino/phpstan-banned-code": "^1.0", + "infection/infection": "^0.26", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5.26", + "qossmic/deptrac-shim": "^1.0", + "rector/rector": "^0.15", + "symfony/phpunit-bridge": "^6.1", + "symplify/easy-coding-standard": "^11.0" }, "type": "library", - "extra": { - "branch-alias": { - "v10.0": "10.0.x-dev", - "v9.0": "9.0.x-dev", - "v8.3": "8.3.x-dev" - } - }, "autoload": { "psr-4": { "OTPHP\\": "src/" @@ -6737,9 +6262,129 @@ ], "support": { "issues": "https://github.com/Spomky-Labs/otphp/issues", - "source": "https://github.com/Spomky-Labs/otphp/tree/v10.0.3" + "source": "https://github.com/Spomky-Labs/otphp/tree/11.2.0" }, - "time": "2022-03-17T08:00:35+00:00" + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2023-03-16T19:16:25+00:00" + }, + { + "name": "spomky-labs/pki-framework", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/Spomky-Labs/pki-framework.git", + "reference": "d3ba688bf40e7c6e0dabf065ee18fc210734e760" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Spomky-Labs/pki-framework/zipball/d3ba688bf40e7c6e0dabf065ee18fc210734e760", + "reference": "d3ba688bf40e7c6e0dabf065ee18fc210734e760", + "shasum": "" + }, + "require": { + "brick/math": "^0.10 || ^0.11", + "ext-mbstring": "*", + "php": ">=8.1" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "ext-gmp": "*", + "ext-openssl": "*", + "infection/infection": "^0.26", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-beberlei-assert": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^10.0", + "rector/rector": "^0.15", + "roave/security-advisories": "dev-latest", + "symfony/phpunit-bridge": "^6.1", + "symfony/var-dumper": "^6.1", + "symplify/easy-coding-standard": "^11.1", + "thecodingmachine/phpstan-safe-rule": "^1.2" + }, + "suggest": { + "ext-bcmath": "For better performance (or GMP)", + "ext-gmp": "For better performance (or BCMath)", + "ext-openssl": "For OpenSSL based cyphering" + }, + "type": "library", + "autoload": { + "psr-4": { + "SpomkyLabs\\Pki\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joni Eskelinen", + "email": "jonieske@gmail.com", + "role": "Original developer" + }, + { + "name": "Florent Morselli", + "email": "florent.morselli@spomky-labs.com", + "role": "Spomky-Labs PKI Framework developer" + } + ], + "description": "A PHP framework for managing Public Key Infrastructures. It comprises X.509 public key certificates, attribute certificates, certification requests and certification path validation.", + "homepage": "https://github.com/spomky-labs/pki-framework", + "keywords": [ + "DER", + "Private Key", + "ac", + "algorithm identifier", + "asn.1", + "asn1", + "attribute certificate", + "certificate", + "certification request", + "cryptography", + "csr", + "decrypt", + "ec", + "encrypt", + "pem", + "pkcs", + "public key", + "rsa", + "sign", + "signature", + "verify", + "x.509", + "x.690", + "x509", + "x690" + ], + "support": { + "issues": "https://github.com/Spomky-Labs/pki-framework/issues", + "source": "https://github.com/Spomky-Labs/pki-framework/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/Spomky", + "type": "github" + }, + { + "url": "https://www.patreon.com/FlorentMorselli", + "type": "patreon" + } + ], + "time": "2023-02-13T17:21:24+00:00" }, { "name": "symfony/apache-pack", @@ -6769,33 +6414,28 @@ }, { "name": "symfony/asset", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/asset.git", - "reference": "1504b6773c6b90118f9871e90a67833b5d1dca3c" + "reference": "b77a4cc8e266b7e0db688de740f9ee7253aa411c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/asset/zipball/1504b6773c6b90118f9871e90a67833b5d1dca3c", - "reference": "1504b6773c6b90118f9871e90a67833b5d1dca3c", + "url": "https://api.github.com/repos/symfony/asset/zipball/b77a4cc8e266b7e0db688de740f9ee7253aa411c", + "reference": "b77a4cc8e266b7e0db688de740f9ee7253aa411c", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, "conflict": { - "symfony/http-foundation": "<5.3" + "symfony/http-foundation": "<5.4" }, "require-dev": { - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/http-foundation": "" + "symfony/http-client": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -6823,7 +6463,7 @@ "description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/asset/tree/v5.4.21" + "source": "https://github.com/symfony/asset/tree/v6.3.0" }, "funding": [ { @@ -6839,62 +6479,61 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-04-21T14:41:17+00:00" }, { "name": "symfony/cache", - "version": "v5.4.23", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "983c79ff28612cdfd66d8e44e1a06e5afc87e107" + "reference": "52cff7608ef6e38376ac11bd1fbb0a220107f066" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/983c79ff28612cdfd66d8e44e1a06e5afc87e107", - "reference": "983c79ff28612cdfd66d8e44e1a06e5afc87e107", + "url": "https://api.github.com/repos/symfony/cache/zipball/52cff7608ef6e38376ac11bd1fbb0a220107f066", + "reference": "52cff7608ef6e38376ac11bd1fbb0a220107f066", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0", + "php": ">=8.1", + "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^1.1.7|^2", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/cache-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3", + "symfony/var-exporter": "^6.2.10" }, "conflict": { "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/var-dumper": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" }, "provide": { - "psr/cache-implementation": "1.0|2.0", - "psr/simple-cache-implementation": "1.0|2.0", - "symfony/cache-implementation": "1.0|2.0" + "psr/cache-implementation": "2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0", + "symfony/cache-implementation": "1.1|2.0|3.0" }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/cache": "^1.6|^2.0", "doctrine/dbal": "^2.13.1|^3.0", - "predis/predis": "^1.1", - "psr/simple-cache": "^1.0|^2.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "predis/predis": "^1.1|^2.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" }, "type": "library", "autoload": { "psr-4": { "Symfony\\Component\\Cache\\": "" }, + "classmap": [ + "Traits/ValueWrapper.php" + ], "exclude-from-classmap": [ "/Tests/" ] @@ -6920,7 +6559,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v5.4.23" + "source": "https://github.com/symfony/cache/tree/v6.3.1" }, "funding": [ { @@ -6936,33 +6575,30 @@ "type": "tidelift" } ], - "time": "2023-04-21T15:38:51+00:00" + "time": "2023-06-24T11:51:27+00:00" }, { "name": "symfony/cache-contracts", - "version": "v2.5.2", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc" + "reference": "ad945640ccc0ae6e208bcea7d7de4b39b569896b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", - "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/ad945640ccc0ae6e208bcea7d7de4b39b569896b", + "reference": "ad945640ccc0ae6e208bcea7d7de4b39b569896b", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/cache": "^1.0|^2.0|^3.0" - }, - "suggest": { - "symfony/cache-implementation": "" + "php": ">=8.1", + "psr/cache": "^3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -6999,7 +6635,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/cache-contracts/tree/v3.3.0" }, "funding": [ { @@ -7015,42 +6651,111 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { - "name": "symfony/config", - "version": "v5.4.21", + "name": "symfony/clock", + "version": "v6.3.1", "source": { "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "2a6b1111d038adfa15d52c0871e540f3b352d1e4" + "url": "https://github.com/symfony/clock.git", + "reference": "2c72817f85cbdd0ae4e49643514a889004934296" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/2a6b1111d038adfa15d52c0871e540f3b352d1e4", - "reference": "2a6b1111d038adfa15d52c0871e540f3b352d1e4", + "url": "https://api.github.com/repos/symfony/clock/zipball/2c72817f85cbdd0ae4e49643514a889004934296", + "reference": "2c72817f85cbdd0ae4e49643514a889004934296", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22" + "php": ">=8.1", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v6.3.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-06-08T23:46:55+00:00" + }, + { + "name": "symfony/config", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "a5e00dec161b08c946a2c16eed02adbeedf827ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/a5e00dec161b08c946a2c16eed02adbeedf827ae", + "reference": "a5e00dec161b08c946a2c16eed02adbeedf827ae", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/filesystem": "^5.4|^6.0", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/finder": "<4.4" + "symfony/finder": "<5.4", + "symfony/service-contracts": "<2.5" }, "require-dev": { - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/yaml": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/yaml": "To use the yaml reference dumper" + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -7078,7 +6783,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v5.4.21" + "source": "https://github.com/symfony/config/tree/v6.3.0" }, "funding": [ { @@ -7094,56 +6799,47 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-04-25T10:46:17+00:00" }, { "name": "symfony/console", - "version": "v5.4.24", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8" + "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8", - "reference": "560fc3ed7a43e6d30ea94a07d77f9a60b8ed0fb8", + "url": "https://api.github.com/repos/symfony/console/zipball/8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", + "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0" }, "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" }, "provide": { - "psr/log-implementation": "1.0|2.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -7177,7 +6873,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.24" + "source": "https://github.com/symfony/console/tree/v6.3.0" }, "funding": [ { @@ -7193,25 +6889,24 @@ "type": "tidelift" } ], - "time": "2023-05-26T05:13:16+00:00" + "time": "2023-05-29T12:49:39+00:00" }, { "name": "symfony/css-selector", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "95f3c7468db1da8cc360b24fa2a26e7cefcb355d" + "reference": "88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/95f3c7468db1da8cc360b24fa2a26e7cefcb355d", - "reference": "95f3c7468db1da8cc360b24fa2a26e7cefcb355d", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf", + "reference": "88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, "type": "library", "autoload": { @@ -7243,7 +6938,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v5.4.21" + "source": "https://github.com/symfony/css-selector/tree/v6.3.0" }, "funding": [ { @@ -7259,52 +6954,44 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-03-20T16:43:42+00:00" }, { "name": "symfony/dependency-injection", - "version": "v5.4.24", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "4645e032d0963fb614969398ca28e47605b1a7da" + "reference": "7abf242af21f196b65f20ab00ff251fdf3889b8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/4645e032d0963fb614969398ca28e47605b1a7da", - "reference": "4645e032d0963fb614969398ca28e47605b1a7da", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/7abf242af21f196b65f20ab00ff251fdf3889b8d", + "reference": "7abf242af21f196b65f20ab00ff251fdf3889b8d", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1.1", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22", - "symfony/service-contracts": "^1.1.6|^2" + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.2.10" }, "conflict": { "ext-psr": "<1.1|>=2", - "symfony/config": "<5.3", - "symfony/finder": "<4.4", - "symfony/proxy-manager-bridge": "<4.4", - "symfony/yaml": "<4.4.26" + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<6.3", + "symfony/yaml": "<5.4" }, "provide": { - "psr/container-implementation": "1.0", - "symfony/service-implementation": "1.0|2.0" + "psr/container-implementation": "1.1|2.0", + "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/config": "^5.3|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4.26|^5.0|^6.0" - }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" + "symfony/config": "^6.1", + "symfony/expression-language": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -7332,7 +7019,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v5.4.24" + "source": "https://github.com/symfony/dependency-injection/tree/v6.3.1" }, "funding": [ { @@ -7348,29 +7035,29 @@ "type": "tidelift" } ], - "time": "2023-05-05T14:42:55+00:00" + "time": "2023-06-24T11:51:27+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v2.5.2", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -7399,7 +7086,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" }, "funding": [ { @@ -7415,79 +7102,73 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/doctrine-bridge", - "version": "v5.4.24", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "1eeb02bcad51cb99ab3b73bc83adf80f9b1a75cf" + "reference": "594263c7d2677022a16e4f39d20070463ba03888" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/1eeb02bcad51cb99ab3b73bc83adf80f9b1a75cf", - "reference": "1eeb02bcad51cb99ab3b73bc83adf80f9b1a75cf", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/594263c7d2677022a16e4f39d20070463ba03888", + "reference": "594263c7d2677022a16e4f39d20070463ba03888", "shasum": "" }, "require": { - "doctrine/event-manager": "~1.0", + "doctrine/event-manager": "^1.2|^2", "doctrine/persistence": "^2|^3", - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/service-contracts": "^2.5|^3" }, "conflict": { + "doctrine/annotations": "<1.13.1", "doctrine/dbal": "<2.13.1", "doctrine/lexer": "<1.1", - "doctrine/orm": "<2.7.4", + "doctrine/orm": "<2.12", "symfony/cache": "<5.4", - "symfony/dependency-injection": "<4.4", + "symfony/dependency-injection": "<6.2", "symfony/form": "<5.4.21|>=6,<6.2.7", - "symfony/http-kernel": "<5", - "symfony/messenger": "<4.4", - "symfony/property-info": "<5", - "symfony/proxy-manager-bridge": "<4.4.19", - "symfony/security-bundle": "<5", - "symfony/security-core": "<5.3", - "symfony/validator": "<5.2" + "symfony/http-foundation": "<6.3", + "symfony/http-kernel": "<6.2", + "symfony/lock": "<6.3", + "symfony/messenger": "<5.4", + "symfony/property-info": "<5.4", + "symfony/security-bundle": "<5.4", + "symfony/security-core": "<6.0", + "symfony/validator": "<5.4.25|>=6,<6.2.12|>=6.3,<6.3.1" }, "require-dev": { - "doctrine/annotations": "^1.10.4|^2", + "doctrine/annotations": "^1.13.1|^2", "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", "doctrine/dbal": "^2.13.1|^3.0", - "doctrine/orm": "^2.7.4", + "doctrine/orm": "^2.12", "psr/log": "^1|^2|^3", "symfony/cache": "^5.4|^6.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/doctrine-messenger": "^5.1|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^6.2", + "symfony/doctrine-messenger": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", "symfony/form": "^5.4.21|^6.2.7", - "symfony/http-kernel": "^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/property-access": "^4.4|^5.0|^6.0", - "symfony/property-info": "^5.0|^6.0", - "symfony/proxy-manager-bridge": "^4.4|^5.0|^6.0", - "symfony/security-core": "^5.3|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/translation": "^4.4|^5.0|^6.0", - "symfony/uid": "^5.1|^6.0", - "symfony/validator": "^5.2|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "doctrine/data-fixtures": "", - "doctrine/dbal": "", - "doctrine/orm": "", - "symfony/form": "", - "symfony/property-info": "", - "symfony/validator": "" + "symfony/http-kernel": "^6.3", + "symfony/lock": "^6.3", + "symfony/messenger": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/proxy-manager-bridge": "^5.4|^6.0", + "symfony/security-core": "^6.0", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^5.4.25|~6.2.12|^6.3.1", + "symfony/var-dumper": "^5.4|^6.0" }, "type": "symfony-bridge", "autoload": { @@ -7515,7 +7196,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v5.4.24" + "source": "https://github.com/symfony/doctrine-bridge/tree/v6.3.1" }, "funding": [ { @@ -7531,29 +7212,32 @@ "type": "tidelift" } ], - "time": "2023-05-25T13:05:00+00:00" + "time": "2023-06-18T20:33:34+00:00" }, { "name": "symfony/dotenv", - "version": "v5.4.22", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/dotenv.git", - "reference": "77b7660bfcb85e8f28287d557d7af0046bcd2ca3" + "reference": "ceadb434fe2a6763a03d2d110441745834f3dd1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dotenv/zipball/77b7660bfcb85e8f28287d557d7af0046bcd2ca3", - "reference": "77b7660bfcb85e8f28287d557d7af0046bcd2ca3", + "url": "https://api.github.com/repos/symfony/dotenv/zipball/ceadb434fe2a6763a03d2d110441745834f3dd1e", + "reference": "ceadb434fe2a6763a03d2d110441745834f3dd1e", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.1" + }, + "conflict": { + "symfony/console": "<5.4", + "symfony/process": "<5.4" }, "require-dev": { - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0" + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -7586,7 +7270,7 @@ "environment" ], "support": { - "source": "https://github.com/symfony/dotenv/tree/v5.4.22" + "source": "https://github.com/symfony/dotenv/tree/v6.3.0" }, "funding": [ { @@ -7602,31 +7286,34 @@ "type": "tidelift" } ], - "time": "2023-03-09T20:36:58+00:00" + "time": "2023-04-21T14:41:17+00:00" }, { "name": "symfony/error-handler", - "version": "v5.4.24", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "c1b9be3b8a6f60f720bec28c4ffb6fb5b00a8946" + "reference": "99d2d814a6351461af350ead4d963bd67451236f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/c1b9be3b8a6f60f720bec28c4ffb6fb5b00a8946", - "reference": "c1b9be3b8a6f60f720bec28c4ffb6fb5b00a8946", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/99d2d814a6351461af350ead4d963bd67451236f", + "reference": "99d2d814a6351461af350ead4d963bd67451236f", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "symfony/var-dumper": "^5.4|^6.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5" }, "require-dev": { - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/serializer": "^4.4|^5.0|^6.0" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -7657,7 +7344,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v5.4.24" + "source": "https://github.com/symfony/error-handler/tree/v6.3.0" }, "funding": [ { @@ -7673,48 +7360,43 @@ "type": "tidelift" } ], - "time": "2023-05-02T16:13:31+00:00" + "time": "2023-05-10T12:03:13+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v5.4.22", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "1df20e45d56da29a4b1d8259dd6e950acbf1b13f" + "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1df20e45d56da29a4b1d8259dd6e950acbf1b13f", - "reference": "1df20e45d56da29a4b1d8259dd6e950acbf1b13f", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", + "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher-contracts": "^2|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" }, "provide": { "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "2.0" + "symfony/event-dispatcher-implementation": "2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/stopwatch": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -7742,7 +7424,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.22" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.0" }, "funding": [ { @@ -7758,33 +7440,30 @@ "type": "tidelift" } ], - "time": "2023-03-17T11:31:58+00:00" + "time": "2023-04-21T14:41:17+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v2.5.2", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1" + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f98b54df6ad059855739db6fcbc2d36995283fe1", - "reference": "f98b54df6ad059855739db6fcbc2d36995283fe1", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/a76aed96a42d2b521153fb382d418e30d18b59df", + "reference": "a76aed96a42d2b521153fb382d418e30d18b59df", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1" }, - "suggest": { - "symfony/event-dispatcher-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -7821,7 +7500,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.0" }, "funding": [ { @@ -7837,26 +7516,27 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/expression-language", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/expression-language.git", - "reference": "501589522b844b8eecf012c133f0404f0eef77ac" + "reference": "6d560c4c80e7e328708efd923f93ad67e6a0c1c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/501589522b844b8eecf012c133f0404f0eef77ac", - "reference": "501589522b844b8eecf012c133f0404f0eef77ac", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/6d560c4c80e7e328708efd923f93ad67e6a0c1c0", + "reference": "6d560c4c80e7e328708efd923f93ad67e6a0c1c0", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/service-contracts": "^1.1|^2|^3" + "php": ">=8.1", + "symfony/cache": "^5.4|^6.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/service-contracts": "^2.5|^3" }, "type": "library", "autoload": { @@ -7884,7 +7564,7 @@ "description": "Provides an engine that can compile and evaluate expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/expression-language/tree/v5.4.21" + "source": "https://github.com/symfony/expression-language/tree/v6.3.0" }, "funding": [ { @@ -7900,27 +7580,26 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-04-28T16:05:33+00:00" }, { "name": "symfony/filesystem", - "version": "v5.4.23", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5" + "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5", - "reference": "b2f79d86cd9e7de0fff6d03baa80eaed7a5f38b5", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", + "reference": "edd36776956f2a6fcf577edb5b05eb0e3bdc52ae", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-mbstring": "~1.8" }, "type": "library", "autoload": { @@ -7948,7 +7627,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.23" + "source": "https://github.com/symfony/filesystem/tree/v6.3.1" }, "funding": [ { @@ -7964,26 +7643,27 @@ "type": "tidelift" } ], - "time": "2023-03-02T11:38:35+00:00" + "time": "2023-06-01T08:30:39+00:00" }, { "name": "symfony/finder", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "078e9a5e1871fcfe6a5ce421b539344c21afef19" + "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/078e9a5e1871fcfe6a5ce421b539344c21afef19", - "reference": "078e9a5e1871fcfe6a5ce421b539344c21afef19", + "url": "https://api.github.com/repos/symfony/finder/zipball/d9b01ba073c44cef617c7907ce2419f8d00d75e2", + "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0" }, "type": "library", "autoload": { @@ -8011,7 +7691,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.21" + "source": "https://github.com/symfony/finder/tree/v6.3.0" }, "funding": [ { @@ -8027,32 +7707,32 @@ "type": "tidelift" } ], - "time": "2023-02-16T09:33:00+00:00" + "time": "2023-04-02T01:25:41+00:00" }, { "name": "symfony/flex", - "version": "v1.20.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "49059a10127ac8270957e116a2251ae535d202ac" + "reference": "3c9c3424efdafe33e0e3cfb5e87e50b34711fedf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/49059a10127ac8270957e116a2251ae535d202ac", - "reference": "49059a10127ac8270957e116a2251ae535d202ac", + "url": "https://api.github.com/repos/symfony/flex/zipball/3c9c3424efdafe33e0e3cfb5e87e50b34711fedf", + "reference": "3c9c3424efdafe33e0e3cfb5e87e50b34711fedf", "shasum": "" }, "require": { - "composer-plugin-api": "^1.0|^2.0", - "php": ">=7.1" + "composer-plugin-api": "^2.1", + "php": ">=8.0" }, "require-dev": { - "composer/composer": "^1.0.2|^2.0", - "symfony/dotenv": "^4.4|^5.0|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/phpunit-bridge": "^4.4.12|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0" + "composer/composer": "^2.1", + "symfony/dotenv": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" }, "type": "composer-plugin", "extra": { @@ -8076,7 +7756,7 @@ "description": "Composer plugin for Symfony", "support": { "issues": "https://github.com/symfony/flex/issues", - "source": "https://github.com/symfony/flex/tree/v1.20.0" + "source": "https://github.com/symfony/flex/tree/v2.3.1" }, "funding": [ { @@ -8092,65 +7772,60 @@ "type": "tidelift" } ], - "time": "2023-05-26T16:25:26+00:00" + "time": "2023-05-27T07:38:25+00:00" }, { "name": "symfony/form", - "version": "v5.4.24", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/form.git", - "reference": "813b79a34ab9843b5a01a6f809f1e4a009aaea2e" + "reference": "59e7c5afef32b9ff735e83e5fc74d63044833a2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/form/zipball/813b79a34ab9843b5a01a6f809f1e4a009aaea2e", - "reference": "813b79a34ab9843b5a01a6f809f1e4a009aaea2e", + "url": "https://api.github.com/repos/symfony/form/zipball/59e7c5afef32b9ff735e83e5fc74d63044833a2b", + "reference": "59e7c5afef32b9ff735e83e5fc74d63044833a2b", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/options-resolver": "^5.1|^6.0", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/options-resolver": "^5.4|^6.0", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-icu": "^1.21", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.23", - "symfony/property-access": "^5.0.8|^6.0", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/property-access": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/console": "<4.4", - "symfony/dependency-injection": "<4.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", "symfony/doctrine-bridge": "<5.4.21|>=6,<6.2.7", - "symfony/error-handler": "<4.4.5", - "symfony/framework-bundle": "<4.4", - "symfony/http-kernel": "<4.4", - "symfony/translation": "<4.4", - "symfony/translation-contracts": "<1.1.7", - "symfony/twig-bridge": "<5.4.21|>=6,<6.2.7" + "symfony/error-handler": "<5.4", + "symfony/framework-bundle": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.3" }, "require-dev": { "doctrine/collections": "^1.0|^2.0", - "symfony/config": "^4.4|^5.0|^6.0", + "symfony/config": "^5.4|^6.0", "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/intl": "^4.4|^5.0|^6.0", - "symfony/security-csrf": "^4.4|^5.0|^6.0", - "symfony/translation": "^4.4|^5.0|^6.0", - "symfony/uid": "^5.1|^6.0", - "symfony/validator": "^4.4.17|^5.1.9|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/security-csrf": "For protecting forms against CSRF attacks.", - "symfony/twig-bridge": "For templating with Twig.", - "symfony/validator": "For form validation." + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/html-sanitizer": "^6.1", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", + "symfony/security-core": "^6.2", + "symfony/security-csrf": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -8178,7 +7853,7 @@ "description": "Allows to easily create, process and reuse HTML forms", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/form/tree/v5.4.24" + "source": "https://github.com/symfony/form/tree/v6.3.0" }, "funding": [ { @@ -8194,114 +7869,108 @@ "type": "tidelift" } ], - "time": "2023-05-25T13:05:00+00:00" + "time": "2023-05-25T13:09:35+00:00" }, { "name": "symfony/framework-bundle", - "version": "v5.4.24", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "c06a56a47817d29318aaace1c655cbde16c998e8" + "reference": "42b0707efba17ca7c6af7373cb1b1a1b4f24f772" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/c06a56a47817d29318aaace1c655cbde16c998e8", - "reference": "c06a56a47817d29318aaace1c655cbde16c998e8", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/42b0707efba17ca7c6af7373cb1b1a1b4f24f772", + "reference": "42b0707efba17ca7c6af7373cb1b1a1b4f24f772", "shasum": "" }, "require": { + "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=7.2.5", - "symfony/cache": "^5.2|^6.0", - "symfony/config": "^5.3|^6.0", - "symfony/dependency-injection": "^5.4.5|^6.0.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/error-handler": "^4.4.1|^5.0.1|^6.0", - "symfony/event-dispatcher": "^5.1|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^5.4.24|^6.2.11", - "symfony/http-kernel": "^5.4|^6.0", + "php": ">=8.1", + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.3.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.1", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-foundation": "^6.3", + "symfony/http-kernel": "^6.3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22", - "symfony/routing": "^5.3|^6.0" + "symfony/routing": "^5.4|^6.0" }, "conflict": { "doctrine/annotations": "<1.13.1", - "doctrine/cache": "<1.11", "doctrine/persistence": "<1.3", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/asset": "<5.3", - "symfony/console": "<5.2.5", - "symfony/dom-crawler": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/form": "<5.2", - "symfony/http-client": "<4.4", - "symfony/lock": "<4.4", - "symfony/mailer": "<5.2", - "symfony/messenger": "<5.4", - "symfony/mime": "<4.4", - "symfony/property-access": "<5.3", - "symfony/property-info": "<4.4", - "symfony/security-csrf": "<5.3", - "symfony/serializer": "<5.2", - "symfony/service-contracts": ">=3.0", - "symfony/stopwatch": "<4.4", - "symfony/translation": "<5.3", - "symfony/twig-bridge": "<4.4", - "symfony/twig-bundle": "<4.4", - "symfony/validator": "<5.2", - "symfony/web-profiler-bundle": "<4.4", - "symfony/workflow": "<5.2" + "symfony/asset": "<5.4", + "symfony/clock": "<6.3", + "symfony/console": "<5.4", + "symfony/dom-crawler": "<6.3", + "symfony/dotenv": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<6.3", + "symfony/lock": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<6.3", + "symfony/mime": "<6.2", + "symfony/property-access": "<5.4", + "symfony/property-info": "<5.4", + "symfony/security-core": "<5.4", + "symfony/security-csrf": "<5.4", + "symfony/serializer": "<6.3", + "symfony/stopwatch": "<5.4", + "symfony/translation": "<6.2.8", + "symfony/twig-bridge": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/validator": "<6.3", + "symfony/web-profiler-bundle": "<5.4", + "symfony/workflow": "<5.4" }, "require-dev": { "doctrine/annotations": "^1.13.1|^2", - "doctrine/cache": "^1.11|^2.0", "doctrine/persistence": "^1.3|^2|^3", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^5.3|^6.0", + "symfony/asset": "^5.4|^6.0", + "symfony/asset-mapper": "^6.3", "symfony/browser-kit": "^5.4|^6.0", + "symfony/clock": "^6.2", "symfony/console": "^5.4.9|^6.0.9", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/dom-crawler": "^4.4.30|^5.3.7|^6.0", - "symfony/dotenv": "^5.1|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/form": "^5.2|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/mailer": "^5.2|^6.0", - "symfony/messenger": "^5.4|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dom-crawler": "^6.3", + "symfony/dotenv": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/html-sanitizer": "^6.1", + "symfony/http-client": "^6.3", + "symfony/lock": "^5.4|^6.0", + "symfony/mailer": "^5.4|^6.0", + "symfony/messenger": "^6.3", + "symfony/mime": "^6.2", "symfony/notifier": "^5.4|^6.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/property-info": "^4.4|^5.0|^6.0", - "symfony/rate-limiter": "^5.2|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/rate-limiter": "^5.4|^6.0", + "symfony/scheduler": "^6.3", "symfony/security-bundle": "^5.4|^6.0", - "symfony/serializer": "^5.4|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/string": "^5.0|^6.0", - "symfony/translation": "^5.3|^6.0", - "symfony/twig-bundle": "^4.4|^5.0|^6.0", - "symfony/validator": "^5.2|^6.0", - "symfony/web-link": "^4.4|^5.0|^6.0", - "symfony/workflow": "^5.2|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/semaphore": "^5.4|^6.0", + "symfony/serializer": "^6.3", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/string": "^5.4|^6.0", + "symfony/translation": "^6.2.8", + "symfony/twig-bundle": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^6.3", + "symfony/web-link": "^5.4|^6.0", + "symfony/workflow": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0", "twig/twig": "^2.10|^3.0" }, - "suggest": { - "ext-apcu": "For best performance of the system caches", - "symfony/console": "For using the console commands", - "symfony/form": "For using forms", - "symfony/property-info": "For using the property_info service", - "symfony/serializer": "For using the serializer service", - "symfony/validator": "For using validation", - "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering", - "symfony/yaml": "For using the debug:config and lint:yaml commands" - }, "type": "symfony-bundle", "autoload": { "psr-4": { @@ -8328,7 +7997,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v5.4.24" + "source": "https://github.com/symfony/framework-bundle/tree/v6.3.1" }, "funding": [ { @@ -8344,36 +8013,38 @@ "type": "tidelift" } ], - "time": "2023-05-25T13:05:00+00:00" + "time": "2023-06-24T09:59:31+00:00" }, { "name": "symfony/http-client", - "version": "v5.4.24", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "9e89ac4c9dfe29f4ed2b10a36e62720286632ad6" + "reference": "1c828a06aef2f5eeba42026dfc532d4fc5406123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/9e89ac4c9dfe29f4ed2b10a36e62720286632ad6", - "reference": "9e89ac4c9dfe29f4ed2b10a36e62720286632ad6", + "url": "https://api.github.com/repos/symfony/http-client/zipball/1c828a06aef2f5eeba42026dfc532d4fc5406123", + "reference": "1c828a06aef2f5eeba42026dfc532d4fc5406123", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/http-client-contracts": "^2.4", - "symfony/polyfill-php73": "^1.11", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.0|^2|^3" + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^3", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.3" }, "provide": { "php-http/async-client-implementation": "*", "php-http/client-implementation": "*", "psr/http-client-implementation": "1.0", - "symfony/http-client-implementation": "2.4" + "symfony/http-client-implementation": "3.0" }, "require-dev": { "amphp/amp": "^2.5", @@ -8383,12 +8054,11 @@ "guzzlehttp/promises": "^1.4", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", - "php-http/message-factory": "^1.0", "psr/http-client": "^1.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4.13|^5.1.5|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0" + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -8419,7 +8089,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v5.4.24" + "source": "https://github.com/symfony/http-client/tree/v6.3.1" }, "funding": [ { @@ -8435,32 +8105,29 @@ "type": "tidelift" } ], - "time": "2023-05-07T13:11:28+00:00" + "time": "2023-06-24T11:51:27+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v2.5.2", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70" + "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", - "reference": "ba6a9f0e8f3edd190520ee3b9a958596b6ca2e70", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/3b66325d0176b4ec826bffab57c9037d759c31fb", + "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb", "shasum": "" }, "require": { - "php": ">=7.2.5" - }, - "suggest": { - "symfony/http-client-implementation": "" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -8470,7 +8137,10 @@ "autoload": { "psr-4": { "Symfony\\Contracts\\HttpClient\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -8497,7 +8167,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.3.0" }, "funding": [ { @@ -8513,40 +8183,41 @@ "type": "tidelift" } ], - "time": "2022-04-12T15:48:08+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { "name": "symfony/http-foundation", - "version": "v5.4.24", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "3c59f97f6249ce552a44f01b93bfcbd786a954f5" + "reference": "e0ad0d153e1c20069250986cd9e9dd1ccebb0d66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3c59f97f6249ce552a44f01b93bfcbd786a954f5", - "reference": "3c59f97f6249ce552a44f01b93bfcbd786a954f5", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e0ad0d153e1c20069250986cd9e9dd1ccebb0d66", + "reference": "e0ad0d153e1c20069250986cd9e9dd1ccebb0d66", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-php83": "^1.27" + }, + "conflict": { + "symfony/cache": "<6.2" }, "require-dev": { - "predis/predis": "~1.0", - "symfony/cache": "^4.4|^5.0|^6.0", + "doctrine/dbal": "^2.13.1|^3.0", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^5.4|^6.0", "symfony/dependency-injection": "^5.4|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^5.4|^6.0", "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", - "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/mime": "^5.4|^6.0", "symfony/rate-limiter": "^5.2|^6.0" }, - "suggest": { - "symfony/mime": "To use the file extension guesser" - }, "type": "library", "autoload": { "psr-4": { @@ -8573,7 +8244,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v5.4.24" + "source": "https://github.com/symfony/http-foundation/tree/v6.3.1" }, "funding": [ { @@ -8589,76 +8260,77 @@ "type": "tidelift" } ], - "time": "2023-05-19T07:21:23+00:00" + "time": "2023-06-24T11:51:27+00:00" }, { "name": "symfony/http-kernel", - "version": "v5.4.24", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f38b722e1557eb3f487d351b48f5a1279b50e9d1" + "reference": "161e16fd2e35fb4881a43bc8b383dfd5be4ac374" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f38b722e1557eb3f487d351b48f5a1279b50e9d1", - "reference": "f38b722e1557eb3f487d351b48f5a1279b50e9d1", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/161e16fd2e35fb4881a43bc8b383dfd5be4ac374", + "reference": "161e16fd2e35fb4881a43bc8b383dfd5be4ac374", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/log": "^1|^2", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^5.0|^6.0", - "symfony/http-foundation": "^5.4.21|^6.2.7", - "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/http-foundation": "^6.2.7", + "symfony/polyfill-ctype": "^1.8" }, "conflict": { "symfony/browser-kit": "<5.4", - "symfony/cache": "<5.0", - "symfony/config": "<5.0", - "symfony/console": "<4.4", - "symfony/dependency-injection": "<5.3", - "symfony/doctrine-bridge": "<5.0", - "symfony/form": "<5.0", - "symfony/http-client": "<5.0", - "symfony/mailer": "<5.0", - "symfony/messenger": "<5.0", - "symfony/translation": "<5.0", - "symfony/twig-bridge": "<5.0", - "symfony/validator": "<5.0", + "symfony/cache": "<5.4", + "symfony/config": "<6.1", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<6.3", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<5.4", + "symfony/validator": "<5.4", + "symfony/var-dumper": "<6.3", "twig/twig": "<2.13" }, "provide": { - "psr/log-implementation": "1.0|2.0" + "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", "symfony/browser-kit": "^5.4|^6.0", - "symfony/config": "^5.0|^6.0", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^5.3|^6.0", - "symfony/dom-crawler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/http-client-contracts": "^1.1|^2|^3", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/translation": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2|^3", + "symfony/clock": "^6.2", + "symfony/config": "^6.1", + "symfony/console": "^5.4|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dependency-injection": "^6.3", + "symfony/dom-crawler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^5.4|^6.0", + "symfony/property-access": "^5.4.5|^6.0.5", + "symfony/routing": "^5.4|^6.0", + "symfony/serializer": "^6.3", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^6.3", + "symfony/var-exporter": "^6.2", "twig/twig": "^2.13|^3.0.4" }, - "suggest": { - "symfony/browser-kit": "", - "symfony/config": "", - "symfony/console": "", - "symfony/dependency-injection": "" - }, "type": "library", "autoload": { "psr-4": { @@ -8685,7 +8357,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v5.4.24" + "source": "https://github.com/symfony/http-kernel/tree/v6.3.1" }, "funding": [ { @@ -8701,41 +8373,34 @@ "type": "tidelift" } ], - "time": "2023-05-27T08:06:30+00:00" + "time": "2023-06-26T06:07:32+00:00" }, { "name": "symfony/intl", - "version": "v5.4.23", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/intl.git", - "reference": "962789bbc76c82c266623321ffc24416f574b636" + "reference": "fdf4aff85fff2cc681cc45936b6b2a52731acc23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/intl/zipball/962789bbc76c82c266623321ffc24416f574b636", - "reference": "962789bbc76c82c266623321ffc24416f574b636", + "url": "https://api.github.com/repos/symfony/intl/zipball/fdf4aff85fff2cc681cc45936b6b2a52731acc23", + "reference": "fdf4aff85fff2cc681cc45936b6b2a52731acc23", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, "require-dev": { - "symfony/filesystem": "^4.4|^5.0|^6.0" + "symfony/filesystem": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0" }, "type": "library", "autoload": { - "files": [ - "Resources/functions.php" - ], "psr-4": { "Symfony\\Component\\Intl\\": "" }, - "classmap": [ - "Resources/stubs" - ], "exclude-from-classmap": [ "/Tests/" ] @@ -8762,7 +8427,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides a PHP replacement layer for the C intl extension that includes additional data from the ICU library", + "description": "Provides access to the localization data of the ICU library", "homepage": "https://symfony.com", "keywords": [ "i18n", @@ -8773,7 +8438,7 @@ "localization" ], "support": { - "source": "https://github.com/symfony/intl/tree/v5.4.23" + "source": "https://github.com/symfony/intl/tree/v6.3.1" }, "funding": [ { @@ -8789,118 +8454,43 @@ "type": "tidelift" } ], - "time": "2023-04-13T10:36:25+00:00" - }, - { - "name": "symfony/lock", - "version": "v5.4.22", - "source": { - "type": "git", - "url": "https://github.com/symfony/lock.git", - "reference": "cc0565235e16ef403097fbd30eba59690bee6b3c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/lock/zipball/cc0565235e16ef403097fbd30eba59690bee6b3c", - "reference": "cc0565235e16ef403097fbd30eba59690bee6b3c", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "doctrine/dbal": "<2.13" - }, - "require-dev": { - "doctrine/dbal": "^2.13|^3.0", - "predis/predis": "~1.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Lock\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jérémy Derussé", - "email": "jeremy@derusse.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Creates and manages locks, a mechanism to provide exclusive access to a shared resource", - "homepage": "https://symfony.com", - "keywords": [ - "cas", - "flock", - "locking", - "mutex", - "redlock", - "semaphore" - ], - "support": { - "source": "https://github.com/symfony/lock/tree/v5.4.22" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-03-10T16:52:09+00:00" + "time": "2023-06-21T12:08:28+00:00" }, { "name": "symfony/mailer", - "version": "v5.4.22", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "6330cd465dfd8b7a07515757a1c37069075f7b0b" + "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/6330cd465dfd8b7a07515757a1c37069075f7b0b", - "reference": "6330cd465dfd8b7a07515757a1c37069075f7b0b", + "url": "https://api.github.com/repos/symfony/mailer/zipball/7b03d9be1dea29bfec0a6c7b603f5072a4c97435", + "reference": "7b03d9be1dea29bfec0a6c7b603f5072a4c97435", "shasum": "" }, "require": { "egulias/email-validator": "^2.1.10|^3|^4", - "php": ">=7.2.5", + "php": ">=8.1", "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/mime": "^5.2.6|^6.0", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3" + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/mime": "^6.2", + "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/http-kernel": "<4.4" + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/messenger": "<6.2", + "symfony/mime": "<6.2", + "symfony/twig-bridge": "<6.2.1" }, "require-dev": { - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0" + "symfony/console": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/messenger": "^6.2", + "symfony/twig-bridge": "^6.2" }, "type": "library", "autoload": { @@ -8928,7 +8518,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v5.4.22" + "source": "https://github.com/symfony/mailer/tree/v6.3.0" }, "funding": [ { @@ -8944,43 +8534,42 @@ "type": "tidelift" } ], - "time": "2023-03-10T10:15:32+00:00" + "time": "2023-05-29T12:49:39+00:00" }, { "name": "symfony/mime", - "version": "v5.4.23", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "ae0a1032a450a3abf305ee44fc55ed423fbf16e3" + "reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/ae0a1032a450a3abf305ee44fc55ed423fbf16e3", - "reference": "ae0a1032a450a3abf305ee44fc55ed423fbf16e3", + "url": "https://api.github.com/repos/symfony/mime/zipball/7b5d2121858cd6efbed778abce9cfdd7ab1f62ad", + "reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0", - "symfony/polyfill-php80": "^1.16" + "symfony/polyfill-mbstring": "^1.0" }, "conflict": { "egulias/email-validator": "~3.0.0", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/mailer": "<4.4", - "symfony/serializer": "<5.4.14|>=6.0,<6.0.14|>=6.1,<6.1.6" + "symfony/mailer": "<5.4", + "symfony/serializer": "<6.2" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/property-access": "^4.4|^5.1|^6.0", - "symfony/property-info": "^4.4|^5.1|^6.0", - "symfony/serializer": "^5.4.14|~6.0.14|^6.1.6" + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/serializer": "^6.2" }, "type": "library", "autoload": { @@ -9012,7 +8601,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v5.4.23" + "source": "https://github.com/symfony/mime/tree/v6.3.0" }, "funding": [ { @@ -9028,47 +8617,41 @@ "type": "tidelift" } ], - "time": "2023-04-19T09:49:13+00:00" + "time": "2023-04-28T15:57:00+00:00" }, { "name": "symfony/monolog-bridge", - "version": "v5.4.22", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/monolog-bridge.git", - "reference": "34be6f0695e4187dbb832a05905fb4c6581ac39a" + "reference": "04b04b8e465e0fa84940e5609b6796a8b4e51bf1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/34be6f0695e4187dbb832a05905fb4c6581ac39a", - "reference": "34be6f0695e4187dbb832a05905fb4c6581ac39a", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/04b04b8e465e0fa84940e5609b6796a8b4e51bf1", + "reference": "04b04b8e465e0fa84940e5609b6796a8b4e51bf1", "shasum": "" }, "require": { - "monolog/monolog": "^1.25.1|^2", - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/http-kernel": "^5.3|^6.0", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3" + "monolog/monolog": "^1.25.1|^2|^3", + "php": ">=8.1", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/console": "<4.4", - "symfony/http-foundation": "<5.3" + "symfony/console": "<5.4", + "symfony/http-foundation": "<5.4", + "symfony/security-core": "<6.0" }, "require-dev": { - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/mailer": "^4.4|^5.0|^6.0", - "symfony/messenger": "^4.4|^5.0|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/security-core": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/console": "For the possibility to show log messages in console commands depending on verbosity settings.", - "symfony/http-kernel": "For using the debugging handlers together with the response life cycle of the HTTP kernel.", - "symfony/var-dumper": "For using the debugging handlers like the console handler or the log server handler." + "symfony/console": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/mailer": "^5.4|^6.0", + "symfony/messenger": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/security-core": "^6.0", + "symfony/var-dumper": "^5.4|^6.0" }, "type": "symfony-bridge", "autoload": { @@ -9096,7 +8679,7 @@ "description": "Provides integration for Monolog with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/monolog-bridge/tree/v5.4.22" + "source": "https://github.com/symfony/monolog-bridge/tree/v6.3.1" }, "funding": [ { @@ -9112,7 +8695,7 @@ "type": "tidelift" } ], - "time": "2023-03-06T21:29:33+00:00" + "time": "2023-06-08T11:13:32+00:00" }, { "name": "symfony/monolog-bundle", @@ -9197,23 +8780,21 @@ }, { "name": "symfony/options-resolver", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9" + "reference": "a10f19f5198d589d5c33333cffe98dc9820332dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9", - "reference": "4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/a10f19f5198d589d5c33333cffe98dc9820332dd", + "reference": "a10f19f5198d589d5c33333cffe98dc9820332dd", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php73": "~1.0", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", "autoload": { @@ -9246,7 +8827,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.4.21" + "source": "https://github.com/symfony/options-resolver/tree/v6.3.0" }, "funding": [ { @@ -9262,32 +8843,31 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-05-12T14:21:09+00:00" }, { "name": "symfony/password-hasher", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/password-hasher.git", - "reference": "7ce4529b2b2ea7de3b6f344a1a41f58201999180" + "reference": "d23ad221989e6b8278d050cabfd7b569eee84590" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/password-hasher/zipball/7ce4529b2b2ea7de3b6f344a1a41f58201999180", - "reference": "7ce4529b2b2ea7de3b6f344a1a41f58201999180", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/d23ad221989e6b8278d050cabfd7b569eee84590", + "reference": "d23ad221989e6b8278d050cabfd7b569eee84590", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.1" }, "conflict": { - "symfony/security-core": "<5.3" + "symfony/security-core": "<5.4" }, "require-dev": { - "symfony/console": "^5.3|^6.0", - "symfony/security-core": "^5.3|^6.0" + "symfony/console": "^5.4|^6.0", + "symfony/security-core": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -9319,7 +8899,7 @@ "password" ], "support": { - "source": "https://github.com/symfony/password-hasher/tree/v5.4.21" + "source": "https://github.com/symfony/password-hasher/tree/v6.3.0" }, "funding": [ { @@ -9335,7 +8915,7 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-02-14T09:04:20+00:00" }, { "name": "symfony/polyfill-ctype", @@ -9917,85 +9497,6 @@ ], "time": "2022-11-03T14:55:06+00:00" }, - { - "name": "symfony/polyfill-php73", - "version": "v1.27.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-11-03T14:55:06+00:00" - }, { "name": "symfony/polyfill-php80", "version": "v1.27.0", @@ -10080,21 +9581,22 @@ "time": "2022-11-03T14:55:06+00:00" }, { - "name": "symfony/polyfill-php81", + "name": "symfony/polyfill-php83", "version": "v1.27.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "508c652ba3ccf69f8c97f251534f229791b52a57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", - "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/508c652ba3ccf69f8c97f251534f229791b52a57", + "reference": "508c652ba3ccf69f8c97f251534f229791b52a57", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.1", + "symfony/polyfill-php80": "^1.14" }, "type": "library", "extra": { @@ -10111,11 +9613,8 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Php81\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Symfony\\Polyfill\\Php83\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -10131,7 +9630,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -10140,7 +9639,89 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/f3cf1a645c2734236ed1e2e671e273eeb3586166", + "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.27.0" }, "funding": [ { @@ -10160,21 +9741,20 @@ }, { "name": "symfony/process", - "version": "v5.4.24", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "e3c46cc5689c8782944274bb30702106ecbe3b64" + "reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/e3c46cc5689c8782944274bb30702106ecbe3b64", - "reference": "e3c46cc5689c8782944274bb30702106ecbe3b64", + "url": "https://api.github.com/repos/symfony/process/zipball/8741e3ed7fe2e91ec099e02446fb86667a0f1628", + "reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, "type": "library", "autoload": { @@ -10202,7 +9782,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.24" + "source": "https://github.com/symfony/process/tree/v6.3.0" }, "funding": [ { @@ -10218,33 +9798,29 @@ "type": "tidelift" } ], - "time": "2023-05-17T11:26:05+00:00" + "time": "2023-05-19T08:06:44+00:00" }, { "name": "symfony/property-access", - "version": "v5.4.22", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", - "reference": "ffee082889586b5718347b291e04071f4d07b38f" + "reference": "db9358571ce63f09c439c2fee6c12e5b090b69ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-access/zipball/ffee082889586b5718347b291e04071f4d07b38f", - "reference": "ffee082889586b5718347b291e04071f4d07b38f", + "url": "https://api.github.com/repos/symfony/property-access/zipball/db9358571ce63f09c439c2fee6c12e5b090b69ac", + "reference": "db9358571ce63f09c439c2fee6c12e5b090b69ac", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/property-info": "^5.2|^6.0" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/property-info": "^5.4|^6.0" }, "require-dev": { - "symfony/cache": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/cache-implementation": "To cache access methods." + "symfony/cache": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -10283,7 +9859,7 @@ "reflection" ], "support": { - "source": "https://github.com/symfony/property-access/tree/v5.4.22" + "source": "https://github.com/symfony/property-access/tree/v6.3.0" }, "funding": [ { @@ -10299,46 +9875,38 @@ "type": "tidelift" } ], - "time": "2023-03-14T14:59:20+00:00" + "time": "2023-05-19T08:06:44+00:00" }, { "name": "symfony/property-info", - "version": "v5.4.24", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "d43b85b00699b4484964c297575b5c6f9dc5f6e1" + "reference": "7f3a03716112269741fe2a809f8f791a371d1fcd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/d43b85b00699b4484964c297575b5c6f9dc5f6e1", - "reference": "d43b85b00699b4484964c297575b5c6f9dc5f6e1", + "url": "https://api.github.com/repos/symfony/property-info/zipball/7f3a03716112269741fe2a809f8f791a371d1fcd", + "reference": "7f3a03716112269741fe2a809f8f791a371d1fcd", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16", - "symfony/string": "^5.1|^6.0" + "php": ">=8.1", + "symfony/string": "^5.4|^6.0" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.2.2", - "phpdocumentor/type-resolver": "<1.4.0", - "symfony/dependency-injection": "<4.4" + "phpdocumentor/reflection-docblock": "<5.2", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/dependency-injection": "<5.4" }, "require-dev": { "doctrine/annotations": "^1.10.4|^2", - "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "phpdocumentor/reflection-docblock": "^5.2", "phpstan/phpdoc-parser": "^1.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/serializer": "^4.4|^5.0|^6.0" - }, - "suggest": { - "phpdocumentor/reflection-docblock": "To use the PHPDoc", - "psr/cache-implementation": "To cache results", - "symfony/doctrine-bridge": "To use Doctrine metadata", - "symfony/serializer": "To use Serializer metadata" + "symfony/cache": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -10374,7 +9942,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v5.4.24" + "source": "https://github.com/symfony/property-info/tree/v6.3.0" }, "funding": [ { @@ -10390,30 +9958,30 @@ "type": "tidelift" } ], - "time": "2023-05-15T20:11:03+00:00" + "time": "2023-05-19T08:06:44+00:00" }, { "name": "symfony/proxy-manager-bridge", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/proxy-manager-bridge.git", - "reference": "a4cf96f3acfa252503a216bea877478f9621c7c0" + "reference": "7ba2ac62c88d7c3460d41f04ceba5fc3b9071a39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/a4cf96f3acfa252503a216bea877478f9621c7c0", - "reference": "a4cf96f3acfa252503a216bea877478f9621c7c0", + "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/7ba2ac62c88d7c3460d41f04ceba5fc3b9071a39", + "reference": "7ba2ac62c88d7c3460d41f04ceba5fc3b9071a39", "shasum": "" }, "require": { "friendsofphp/proxy-manager-lts": "^1.0.2", - "php": ">=7.2.5", - "symfony/dependency-injection": "^5.0|^6.0", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/dependency-injection": "^6.3", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/config": "^4.4|^5.0|^6.0" + "symfony/config": "^6.1" }, "type": "symfony-bridge", "autoload": { @@ -10441,7 +10009,7 @@ "description": "Provides integration for ProxyManager with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/proxy-manager-bridge/tree/v5.4.21" + "source": "https://github.com/symfony/proxy-manager-bridge/tree/v6.3.0" }, "funding": [ { @@ -10457,7 +10025,7 @@ "type": "tidelift" } ], - "time": "2023-02-16T09:33:00+00:00" + "time": "2023-05-26T07:49:33+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -10549,25 +10117,25 @@ }, { "name": "symfony/rate-limiter", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/rate-limiter.git", - "reference": "342acb2d23f6012f6150e7a8b167bf9cd931c0f8" + "reference": "a8aff626821721a3b2e64dbda6a3f1ee7bab6d80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/342acb2d23f6012f6150e7a8b167bf9cd931c0f8", - "reference": "342acb2d23f6012f6150e7a8b167bf9cd931c0f8", + "url": "https://api.github.com/repos/symfony/rate-limiter/zipball/a8aff626821721a3b2e64dbda6a3f1ee7bab6d80", + "reference": "a8aff626821721a3b2e64dbda6a3f1ee7bab6d80", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/lock": "^5.2|^6.0", - "symfony/options-resolver": "^5.1|^6.0" + "php": ">=8.1", + "symfony/options-resolver": "^5.4|^6.0" }, "require-dev": { - "psr/cache": "^1.0|^2.0|^3.0" + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/lock": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -10599,7 +10167,7 @@ "rate-limiter" ], "support": { - "source": "https://github.com/symfony/rate-limiter/tree/v5.4.21" + "source": "https://github.com/symfony/rate-limiter/tree/v6.3.0" }, "funding": [ { @@ -10615,47 +10183,39 @@ "type": "tidelift" } ], - "time": "2023-02-21T19:46:44+00:00" + "time": "2023-04-24T14:22:26+00:00" }, { "name": "symfony/routing", - "version": "v5.4.22", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "c2ac11eb34947999b7c38fb4c835a57306907e6d" + "reference": "d37ad1779c38b8eb71996d17dc13030dcb7f9cf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/c2ac11eb34947999b7c38fb4c835a57306907e6d", - "reference": "c2ac11eb34947999b7c38fb4c835a57306907e6d", + "url": "https://api.github.com/repos/symfony/routing/zipball/d37ad1779c38b8eb71996d17dc13030dcb7f9cf5", + "reference": "d37ad1779c38b8eb71996d17dc13030dcb7f9cf5", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, "conflict": { "doctrine/annotations": "<1.12", - "symfony/config": "<5.3", - "symfony/dependency-injection": "<4.4", - "symfony/yaml": "<4.4" + "symfony/config": "<6.2", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" }, "require-dev": { "doctrine/annotations": "^1.12|^2", "psr/log": "^1|^2|^3", - "symfony/config": "^5.3|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/config": "For using the all-in-one router or any loader", - "symfony/expression-language": "For using expression matching", - "symfony/http-foundation": "For using a Symfony Request object", - "symfony/yaml": "For using the YAML loader" + "symfony/config": "^6.2", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -10689,7 +10249,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v5.4.22" + "source": "https://github.com/symfony/routing/tree/v6.3.1" }, "funding": [ { @@ -10705,36 +10265,35 @@ "type": "tidelift" } ], - "time": "2023-03-14T14:59:20+00:00" + "time": "2023-06-05T15:30:22+00:00" }, { "name": "symfony/runtime", - "version": "v5.4.22", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "4a78e519d40a3845437e29dc514959631badfed4" + "reference": "8e83b5d8e0ace903e1a91dedfe08a84ed2a54b0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/4a78e519d40a3845437e29dc514959631badfed4", - "reference": "4a78e519d40a3845437e29dc514959631badfed4", + "url": "https://api.github.com/repos/symfony/runtime/zipball/8e83b5d8e0ace903e1a91dedfe08a84ed2a54b0d", + "reference": "8e83b5d8e0ace903e1a91dedfe08a84ed2a54b0d", "shasum": "" }, "require": { "composer-plugin-api": "^1.0|^2.0", - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.1" }, "conflict": { - "symfony/dotenv": "<5.1" + "symfony/dotenv": "<5.4" }, "require-dev": { "composer/composer": "^1.0.2|^2.0", - "symfony/console": "^4.4.30|^5.3.7|^6.0", - "symfony/dotenv": "^5.1|^6.0", - "symfony/http-foundation": "^4.4.30|^5.3.7|^6.0", - "symfony/http-kernel": "^4.4.30|^5.3.7|^6.0" + "symfony/console": "^5.4.9|^6.0.9", + "symfony/dotenv": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0" }, "type": "composer-plugin", "extra": { @@ -10769,7 +10328,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v5.4.22" + "source": "https://github.com/symfony/runtime/tree/v6.3.1" }, "funding": [ { @@ -10785,65 +10344,72 @@ "type": "tidelift" } ], - "time": "2023-03-14T15:48:23+00:00" + "time": "2023-06-21T12:08:28+00:00" }, { "name": "symfony/security-bundle", - "version": "v5.4.22", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "36eddff8266126de032ab528417ad13eb43f6cb5" + "reference": "f4fe79d7ebafd406e1a6f646839bfbbed641d8b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/36eddff8266126de032ab528417ad13eb43f6cb5", - "reference": "36eddff8266126de032ab528417ad13eb43f6cb5", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/f4fe79d7ebafd406e1a6f646839bfbbed641d8b2", + "reference": "f4fe79d7ebafd406e1a6f646839bfbbed641d8b2", "shasum": "" }, "require": { + "composer-runtime-api": ">=2.1", "ext-xml": "*", - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^5.3|^6.0", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher": "^5.1|^6.0", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/http-kernel": "^5.3|^6.0", - "symfony/password-hasher": "^5.3|^6.0", - "symfony/polyfill-php80": "^1.16", - "symfony/security-core": "^5.4|^6.0", - "symfony/security-csrf": "^4.4|^5.0|^6.0", - "symfony/security-guard": "^5.3", - "symfony/security-http": "^5.4.20|~6.0.20|~6.1.12|^6.2.6" + "php": ">=8.1", + "symfony/clock": "^6.3", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.2", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/http-foundation": "^6.2", + "symfony/http-kernel": "^6.2", + "symfony/password-hasher": "^5.4|^6.0", + "symfony/security-core": "^6.2", + "symfony/security-csrf": "^5.4|^6.0", + "symfony/security-http": "^6.3" }, "conflict": { - "symfony/browser-kit": "<4.4", - "symfony/console": "<4.4", - "symfony/framework-bundle": "<4.4", - "symfony/ldap": "<5.1", - "symfony/twig-bundle": "<4.4" + "symfony/browser-kit": "<5.4", + "symfony/console": "<5.4", + "symfony/framework-bundle": "<6.3", + "symfony/http-client": "<5.4", + "symfony/ldap": "<5.4", + "symfony/twig-bundle": "<5.4" }, "require-dev": { "doctrine/annotations": "^1.10.4|^2", - "symfony/asset": "^4.4|^5.0|^6.0", - "symfony/browser-kit": "^4.4|^5.0|^6.0", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/dom-crawler": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/form": "^4.4|^5.0|^6.0", - "symfony/framework-bundle": "^5.3|^6.0", - "symfony/ldap": "^5.3|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/rate-limiter": "^5.2|^6.0", - "symfony/serializer": "^4.4|^5.0|^6.0", - "symfony/translation": "^4.4|^5.0|^6.0", - "symfony/twig-bridge": "^4.4|^5.0|^6.0", - "symfony/twig-bundle": "^4.4|^5.0|^6.0", - "symfony/validator": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0", - "twig/twig": "^2.13|^3.0.4" + "symfony/asset": "^5.4|^6.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dom-crawler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/framework-bundle": "^6.3", + "symfony/http-client": "^5.4|^6.0", + "symfony/ldap": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/rate-limiter": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/twig-bridge": "^5.4|^6.0", + "symfony/twig-bundle": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4", + "web-token/jwt-checker": "^3.1", + "web-token/jwt-signature-algorithm-ecdsa": "^3.1", + "web-token/jwt-signature-algorithm-eddsa": "^3.1", + "web-token/jwt-signature-algorithm-hmac": "^3.1", + "web-token/jwt-signature-algorithm-none": "^3.1", + "web-token/jwt-signature-algorithm-rsa": "^3.1" }, "type": "symfony-bundle", "autoload": { @@ -10871,7 +10437,7 @@ "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-bundle/tree/v5.4.22" + "source": "https://github.com/symfony/security-bundle/tree/v6.3.1" }, "funding": [ { @@ -10887,56 +10453,47 @@ "type": "tidelift" } ], - "time": "2023-03-10T10:02:45+00:00" + "time": "2023-06-21T12:08:28+00:00" }, { "name": "symfony/security-core", - "version": "v5.4.22", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "a801d525c7545332e2ddf7f52c163959354b1650" + "reference": "9cb74232e978be1440d2bb7daf91eb40a9363890" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/a801d525c7545332e2ddf7f52c163959354b1650", - "reference": "a801d525c7545332e2ddf7f52c163959354b1650", + "url": "https://api.github.com/repos/symfony/security-core/zipball/9cb74232e978be1440d2bb7daf91eb40a9363890", + "reference": "9cb74232e978be1440d2bb7daf91eb40a9363890", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/event-dispatcher-contracts": "^1.1|^2|^3", - "symfony/password-hasher": "^5.3|^6.0", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1.6|^2|^3" + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/password-hasher": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/event-dispatcher": "<4.4", - "symfony/http-foundation": "<5.3", - "symfony/ldap": "<4.4", - "symfony/security-guard": "<4.4", - "symfony/validator": "<5.2" + "symfony/event-dispatcher": "<5.4", + "symfony/http-foundation": "<5.4", + "symfony/ldap": "<5.4", + "symfony/security-guard": "<5.4", + "symfony/validator": "<5.4" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "psr/container": "^1.0|^2.0", + "psr/container": "^1.1|^2.0", "psr/log": "^1|^2|^3", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/ldap": "^4.4|^5.0|^6.0", - "symfony/translation": "^4.4|^5.0|^6.0", - "symfony/validator": "^5.2|^6.0" - }, - "suggest": { - "psr/container-implementation": "To instantiate the Security class", - "symfony/event-dispatcher": "", - "symfony/expression-language": "For using the expression voter", - "symfony/http-foundation": "", - "symfony/ldap": "For using LDAP integration", - "symfony/validator": "For using the user password constraint" + "symfony/cache": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/ldap": "^5.4|^6.0", + "symfony/string": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -10964,7 +10521,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v5.4.22" + "source": "https://github.com/symfony/security-core/tree/v6.3.0" }, "funding": [ { @@ -10980,35 +10537,31 @@ "type": "tidelift" } ], - "time": "2023-03-10T10:02:45+00:00" + "time": "2023-04-28T15:57:00+00:00" }, { "name": "symfony/security-csrf", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "776a538e5f20fb560a182f790979c71455694203" + "reference": "1f505c9060bde692eb37718c78a91d95d9abeeec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/776a538e5f20fb560a182f790979c71455694203", - "reference": "776a538e5f20fb560a182f790979c71455694203", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/1f505c9060bde692eb37718c78a91d95d9abeeec", + "reference": "1f505c9060bde692eb37718c78a91d95d9abeeec", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16", - "symfony/security-core": "^4.4|^5.0|^6.0" + "php": ">=8.1", + "symfony/security-core": "^5.4|^6.0" }, "conflict": { - "symfony/http-foundation": "<5.3" + "symfony/http-foundation": "<5.4" }, "require-dev": { - "symfony/http-foundation": "^5.3|^6.0" - }, - "suggest": { - "symfony/http-foundation": "For using the class SessionTokenStorage." + "symfony/http-foundation": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -11036,7 +10589,7 @@ "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-csrf/tree/v5.4.21" + "source": "https://github.com/symfony/security-csrf/tree/v6.3.0" }, "funding": [ { @@ -11052,115 +10605,50 @@ "type": "tidelift" } ], - "time": "2023-02-16T09:33:00+00:00" - }, - { - "name": "symfony/security-guard", - "version": "v5.4.22", - "source": { - "type": "git", - "url": "https://github.com/symfony/security-guard.git", - "reference": "62d064b1ee682e4617f4c5ddc0d31f73e1a7ecaa" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/security-guard/zipball/62d064b1ee682e4617f4c5ddc0d31f73e1a7ecaa", - "reference": "62d064b1ee682e4617f4c5ddc0d31f73e1a7ecaa", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15", - "symfony/security-core": "^5.0", - "symfony/security-http": "^5.3" - }, - "require-dev": { - "psr/log": "^1|^2|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Security\\Guard\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Security Component - Guard", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/security-guard/tree/v5.4.22" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2023-03-06T21:29:33+00:00" + "time": "2023-04-21T14:41:17+00:00" }, { "name": "symfony/security-http", - "version": "v5.4.23", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "6791856229cc605834d169091981e4eae77dad45" + "reference": "36d2bdd09c33f63014dc65f164a77ff099d256c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/6791856229cc605834d169091981e4eae77dad45", - "reference": "6791856229cc605834d169091981e4eae77dad45", + "url": "https://api.github.com/repos/symfony/security-http/zipball/36d2bdd09c33f63014dc65f164a77ff099d256c6", + "reference": "36d2bdd09c33f63014dc65f164a77ff099d256c6", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/http-kernel": "^5.3|^6.0", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^6.3", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/property-access": "^4.4|^5.0|^6.0", - "symfony/security-core": "^5.4.19|~6.0.19|~6.1.11|^6.2.5" + "symfony/property-access": "^5.4|^6.0", + "symfony/security-core": "^6.3" }, "conflict": { - "symfony/event-dispatcher": "<4.3", - "symfony/security-bundle": "<5.3", - "symfony/security-csrf": "<4.4" + "symfony/clock": "<6.3", + "symfony/event-dispatcher": "<5.4.9|>=6,<6.0.9", + "symfony/http-client-contracts": "<3.0", + "symfony/security-bundle": "<5.4", + "symfony/security-csrf": "<5.4" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/rate-limiter": "^5.2|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/security-csrf": "^4.4|^5.0|^6.0", - "symfony/translation": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs", - "symfony/security-csrf": "For using tokens to protect authentication/logout attempts" + "symfony/cache": "^5.4|^6.0", + "symfony/clock": "^6.3", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-client-contracts": "^3.0", + "symfony/rate-limiter": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", + "symfony/security-csrf": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "web-token/jwt-checker": "^3.1", + "web-token/jwt-signature-algorithm-ecdsa": "^3.1" }, "type": "library", "autoload": { @@ -11188,7 +10676,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v5.4.23" + "source": "https://github.com/symfony/security-http/tree/v6.3.1" }, "funding": [ { @@ -11204,66 +10692,56 @@ "type": "tidelift" } ], - "time": "2023-04-21T11:34:27+00:00" + "time": "2023-06-18T15:50:12+00:00" }, { "name": "symfony/serializer", - "version": "v5.4.24", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "12535bb7b1d3b53802bf18d61a98bb1145fabcdb" + "reference": "1d238ee3180bc047f8ab713bfb73848d553f4407" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/12535bb7b1d3b53802bf18d61a98bb1145fabcdb", - "reference": "12535bb7b1d3b53802bf18d61a98bb1145fabcdb", + "url": "https://api.github.com/repos/symfony/serializer/zipball/1d238ee3180bc047f8ab713bfb73848d553f4407", + "reference": "1d238ee3180bc047f8ab713bfb73848d553f4407", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { "doctrine/annotations": "<1.12", "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/dependency-injection": "<4.4", + "symfony/dependency-injection": "<5.4", "symfony/property-access": "<5.4", - "symfony/property-info": "<5.3.13", - "symfony/uid": "<5.3", - "symfony/yaml": "<4.4" + "symfony/property-info": "<5.4", + "symfony/uid": "<5.4", + "symfony/yaml": "<5.4" }, "require-dev": { "doctrine/annotations": "^1.12|^2", "phpdocumentor/reflection-docblock": "^3.2|^4.0|^5.0", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/filesystem": "^4.4|^5.0|^6.0", - "symfony/form": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0", + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/filesystem": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", "symfony/property-access": "^5.4|^6.0", - "symfony/property-info": "^5.3.13|^6.0", - "symfony/uid": "^5.3|^6.0", - "symfony/validator": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0", - "symfony/var-exporter": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/cache-implementation": "For using the metadata cache.", - "symfony/config": "For using the XML mapping loader.", - "symfony/mime": "For using a MIME type guesser within the DataUriNormalizer.", - "symfony/property-access": "For using the ObjectNormalizer.", - "symfony/property-info": "To deserialize relations.", - "symfony/var-exporter": "For using the metadata compiler.", - "symfony/yaml": "For using the default YAML mapping loader." + "symfony/property-info": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0", + "symfony/var-exporter": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -11291,7 +10769,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v5.4.24" + "source": "https://github.com/symfony/serializer/tree/v6.3.1" }, "funding": [ { @@ -11307,37 +10785,33 @@ "type": "tidelift" } ], - "time": "2023-05-12T08:37:35+00:00" + "time": "2023-06-21T19:54:33+00:00" }, { "name": "symfony/service-contracts", - "version": "v2.5.2", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=8.1", + "psr/container": "^2.0" }, "conflict": { "ext-psr": "<1.1|>=2" }, - "suggest": { - "symfony/service-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -11347,7 +10821,10 @@ "autoload": { "psr-4": { "Symfony\\Contracts\\Service\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -11374,7 +10851,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" }, "funding": [ { @@ -11390,25 +10867,93 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:17:29+00:00" + "time": "2023-05-23T14:45:45+00:00" }, { - "name": "symfony/stopwatch", - "version": "v5.4.21", + "name": "symfony/stimulus-bundle", + "version": "v2.9.1", "source": { "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "f83692cd869a6f2391691d40a01e8acb89e76fee" + "url": "https://github.com/symfony/stimulus-bundle.git", + "reference": "5261003352f52d9e1bcd0a04f099784ada2c3829" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/f83692cd869a6f2391691d40a01e8acb89e76fee", - "reference": "f83692cd869a6f2391691d40a01e8acb89e76fee", + "url": "https://api.github.com/repos/symfony/stimulus-bundle/zipball/5261003352f52d9e1bcd0a04f099784ada2c3829", + "reference": "5261003352f52d9e1bcd0a04f099784ada2c3829", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1|^2|^3" + "php": ">=8.1", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "twig/twig": "^2.15.3|^3.4.3" + }, + "require-dev": { + "symfony/asset-mapper": "6.3.x-dev", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/twig-bundle": "^5.4|^6.0", + "zenstruck/browser": "^1.4" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\UX\\StimulusBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Integration with your Symfony app & Stimulus!", + "keywords": [ + "symfony-ux" + ], + "support": { + "source": "https://github.com/symfony/stimulus-bundle/tree/v2.9.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-30T15:13:12+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", + "reference": "fc47f1015ec80927ff64ba9094dfe8b9d48fe9f2", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/service-contracts": "^2.5|^3" }, "type": "library", "autoload": { @@ -11436,7 +10981,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.4.21" + "source": "https://github.com/symfony/stopwatch/tree/v6.3.0" }, "funding": [ { @@ -11452,38 +10997,38 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-02-16T10:14:28+00:00" }, { "name": "symfony/string", - "version": "v5.4.22", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62" + "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", - "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", + "url": "https://api.github.com/repos/symfony/string/zipball/f2e190ee75ff0f5eced645ec0be5c66fac81f51f", + "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f", "shasum": "" }, "require": { - "php": ">=7.2.5", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/translation-contracts": ">=3.0" + "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/intl": "^6.2", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -11522,7 +11067,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.22" + "source": "https://github.com/symfony/string/tree/v6.3.0" }, "funding": [ { @@ -11538,57 +11083,54 @@ "type": "tidelift" } ], - "time": "2023-03-14T06:11:53+00:00" + "time": "2023-03-21T21:06:29+00:00" }, { "name": "symfony/translation", - "version": "v5.4.24", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "de237e59c5833422342be67402d487fbf50334ff" + "reference": "f72b2cba8f79dd9d536f534f76874b58ad37876f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/de237e59c5833422342be67402d487fbf50334ff", - "reference": "de237e59c5833422342be67402d487fbf50334ff", + "url": "https://api.github.com/repos/symfony/translation/zipball/f72b2cba8f79dd9d536f534f76874b58ad37876f", + "reference": "f72b2cba8f79dd9d536f534f76874b58ad37876f", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/translation-contracts": "^2.3" + "symfony/translation-contracts": "^2.5|^3.0" }, "conflict": { - "symfony/config": "<4.4", - "symfony/console": "<5.3", - "symfony/dependency-injection": "<5.0", - "symfony/http-kernel": "<5.0", - "symfony/twig-bundle": "<5.0", - "symfony/yaml": "<4.4" + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" }, "provide": { - "symfony/translation-implementation": "2.3" + "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { + "nikic/php-parser": "^4.13", "psr/log": "^1|^2|^3", - "symfony/config": "^4.4|^5.0|^6.0", + "symfony/config": "^5.4|^6.0", "symfony/console": "^5.4|^6.0", - "symfony/dependency-injection": "^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/http-client-contracts": "^1.1|^2.0|^3.0", - "symfony/http-kernel": "^5.0|^6.0", - "symfony/intl": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", "symfony/polyfill-intl-icu": "^1.21", - "symfony/service-contracts": "^1.1.2|^2|^3", - "symfony/yaml": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log-implementation": "To use logging capability in translator", - "symfony/config": "", - "symfony/yaml": "" + "symfony/routing": "^5.4|^6.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -11619,7 +11161,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v5.4.24" + "source": "https://github.com/symfony/translation/tree/v6.3.0" }, "funding": [ { @@ -11635,32 +11177,29 @@ "type": "tidelift" } ], - "time": "2023-05-19T12:34:17+00:00" + "time": "2023-05-19T12:46:45+00:00" }, { "name": "symfony/translation-contracts", - "version": "v2.5.2", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe" + "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/136b19dd05cdf0709db6537d058bcab6dd6e2dbe", - "reference": "136b19dd05cdf0709db6537d058bcab6dd6e2dbe", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/02c24deb352fb0d79db5486c0c79905a85e37e86", + "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86", "shasum": "" }, "require": { - "php": ">=7.2.5" - }, - "suggest": { - "symfony/translation-implementation": "" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "3.4-dev" }, "thanks": { "name": "symfony/contracts", @@ -11670,7 +11209,10 @@ "autoload": { "psr-4": { "Symfony\\Contracts\\Translation\\": "" - } + }, + "exclude-from-classmap": [ + "/Test/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -11697,7 +11239,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v2.5.2" + "source": "https://github.com/symfony/translation-contracts/tree/v3.3.0" }, "funding": [ { @@ -11713,85 +11255,72 @@ "type": "tidelift" } ], - "time": "2022-06-27T16:58:25+00:00" + "time": "2023-05-30T17:17:10+00:00" }, { "name": "symfony/twig-bridge", - "version": "v5.4.22", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "e5b174464f68be6876046db3ad6e217d9a7dbbac" + "reference": "67a33c71062d7d931fe9a8cb7be79cca986a6c09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/e5b174464f68be6876046db3ad6e217d9a7dbbac", - "reference": "e5b174464f68be6876046db3ad6e217d9a7dbbac", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/67a33c71062d7d931fe9a8cb7be79cca986a6c09", + "reference": "67a33c71062d7d931fe9a8cb7be79cca986a6c09", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16", - "symfony/translation-contracts": "^1.1|^2|^3", + "php": ">=8.1", + "symfony/translation-contracts": "^2.5|^3", "twig/twig": "^2.13|^3.0.4" }, "conflict": { "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/console": "<5.3", - "symfony/form": "<5.4.21|>=6,<6.2.7", - "symfony/http-foundation": "<5.3", - "symfony/http-kernel": "<4.4", - "symfony/translation": "<5.2", - "symfony/workflow": "<5.2" + "symfony/console": "<5.4", + "symfony/form": "<6.3", + "symfony/http-foundation": "<5.4", + "symfony/http-kernel": "<6.2", + "symfony/mime": "<6.2", + "symfony/translation": "<5.4", + "symfony/workflow": "<5.4" }, "require-dev": { "doctrine/annotations": "^1.12|^2", "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^4.4|^5.0|^6.0", - "symfony/console": "^5.3|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/form": "^5.4.21|^6.2.7", - "symfony/http-foundation": "^5.3|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/intl": "^4.4|^5.0|^6.0", - "symfony/mime": "^5.2|^6.0", + "symfony/asset": "^5.4|^6.0", + "symfony/asset-mapper": "^6.3", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/form": "^6.3", + "symfony/html-sanitizer": "^6.1", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^6.2", + "symfony/intl": "^5.4|^6.0", + "symfony/mime": "^6.2", "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^4.4|^5.1|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^4.4|^5.0|^6.0", - "symfony/security-csrf": "^4.4|^5.0|^6.0", - "symfony/security-http": "^4.4|^5.0|^6.0", - "symfony/serializer": "^5.2|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/translation": "^5.2|^6.0", - "symfony/web-link": "^4.4|^5.0|^6.0", - "symfony/workflow": "^5.2|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0", + "symfony/security-core": "^5.4|^6.0", + "symfony/security-csrf": "^5.4|^6.0", + "symfony/security-http": "^5.4|^6.0", + "symfony/serializer": "^6.2", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^6.1", + "symfony/web-link": "^5.4|^6.0", + "symfony/workflow": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0", "twig/cssinliner-extra": "^2.12|^3", "twig/inky-extra": "^2.12|^3", "twig/markdown-extra": "^2.12|^3" }, - "suggest": { - "symfony/asset": "For using the AssetExtension", - "symfony/expression-language": "For using the ExpressionExtension", - "symfony/finder": "", - "symfony/form": "For using the FormExtension", - "symfony/http-kernel": "For using the HttpKernelExtension", - "symfony/routing": "For using the RoutingExtension", - "symfony/security-core": "For using the SecurityExtension", - "symfony/security-csrf": "For using the CsrfExtension", - "symfony/security-http": "For using the LogoutUrlExtension", - "symfony/stopwatch": "For using the StopwatchExtension", - "symfony/translation": "For using the TranslationExtension", - "symfony/var-dumper": "For using the DumpExtension", - "symfony/web-link": "For using the WebLinkExtension", - "symfony/yaml": "For using the YamlExtension" - }, "type": "symfony-bridge", "autoload": { "psr-4": { @@ -11818,7 +11347,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v5.4.22" + "source": "https://github.com/symfony/twig-bridge/tree/v6.3.0" }, "funding": [ { @@ -11834,52 +11363,48 @@ "type": "tidelift" } ], - "time": "2023-03-31T08:28:44+00:00" + "time": "2023-05-29T13:12:36+00:00" }, { "name": "symfony/twig-bundle", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "875d0edfc8df7505c1993419882c4071fc28c477" + "reference": "d0cd4d1675c0582d27c2e8bb0dc27c0303d8e3ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/875d0edfc8df7505c1993419882c4071fc28c477", - "reference": "875d0edfc8df7505c1993419882c4071fc28c477", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/d0cd4d1675c0582d27c2e8bb0dc27c0303d8e3ea", + "reference": "d0cd4d1675c0582d27c2e8bb0dc27c0303d8e3ea", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^5.0|^6.0", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16", - "symfony/twig-bridge": "^5.3|^6.0", + "composer-runtime-api": ">=2.1", + "php": ">=8.1", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.1", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^6.2", + "symfony/twig-bridge": "^6.3", "twig/twig": "^2.13|^3.0.4" }, "conflict": { - "symfony/dependency-injection": "<5.3", - "symfony/framework-bundle": "<5.0", - "symfony/service-contracts": ">=3.0", - "symfony/translation": "<5.0" + "symfony/framework-bundle": "<5.4", + "symfony/translation": "<5.4" }, "require-dev": { "doctrine/annotations": "^1.10.4|^2", - "doctrine/cache": "^1.0|^2.0", - "symfony/asset": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^5.3|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/form": "^4.4|^5.0|^6.0", - "symfony/framework-bundle": "^5.0|^6.0", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0", - "symfony/translation": "^5.0|^6.0", - "symfony/web-link": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" + "symfony/asset": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/web-link": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" }, "type": "symfony-bundle", "autoload": { @@ -11907,7 +11432,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v5.4.21" + "source": "https://github.com/symfony/twig-bundle/tree/v6.3.0" }, "funding": [ { @@ -11923,47 +11448,122 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-05-06T09:53:41+00:00" }, { - "name": "symfony/ux-turbo", - "version": "v2.8.1", + "name": "symfony/uid", + "version": "v6.3.0", "source": { "type": "git", - "url": "https://github.com/symfony/ux-turbo.git", - "reference": "3b2716284ba3153d911e53f87db03aef40ff0efd" + "url": "https://github.com/symfony/uid.git", + "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/3b2716284ba3153d911e53f87db03aef40ff0efd", - "reference": "3b2716284ba3153d911e53f87db03aef40ff0efd", + "url": "https://api.github.com/repos/symfony/uid/zipball/01b0f20b1351d997711c56f1638f7a8c3061e384", + "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/webpack-encore-bundle": "^1.11" + "php": ">=8.1", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v6.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-04-08T07:25:02+00:00" + }, + { + "name": "symfony/ux-turbo", + "version": "v2.9.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/ux-turbo.git", + "reference": "32c68183b8e0c18130be53b679207e3a43f10f25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/ux-turbo/zipball/32c68183b8e0c18130be53b679207e3a43f10f25", + "reference": "32c68183b8e0c18130be53b679207e3a43f10f25", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/stimulus-bundle": "^2.9.1" }, "conflict": { "symfony/flex": "<1.13" }, "require-dev": { "doctrine/annotations": "^1.12", - "doctrine/doctrine-bundle": "^2.2", + "doctrine/doctrine-bundle": "^2.4.3", "doctrine/orm": "^2.8 | 3.0", - "phpstan/phpstan": "^0.12", - "symfony/debug-bundle": "^5.2|^6.0", + "phpstan/phpstan": "^1.10", + "symfony/debug-bundle": "^5.4|^6.0", "symfony/expression-language": "^5.4|^6.0", - "symfony/form": "^5.2|^6.0", - "symfony/framework-bundle": "^5.2|^6.0", - "symfony/mercure-bundle": "^0.3", - "symfony/messenger": "^5.2|^6.0", + "symfony/form": "^5.4|^6.0", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/mercure-bundle": "^0.3.4", + "symfony/messenger": "^5.4|^6.0", "symfony/panther": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.2.1|^6.0", - "symfony/property-access": "^5.2|^6.0", - "symfony/security-core": "^5.2|^6.0", - "symfony/stopwatch": "^5.2|^6.0", - "symfony/twig-bundle": "^5.2|^6.0", - "symfony/web-profiler-bundle": "^5.2|^6.0" + "symfony/phpunit-bridge": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/security-core": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/twig-bundle": "^5.4|^6.0", + "symfony/web-profiler-bundle": "^5.4|^6.0", + "symfony/webpack-encore-bundle": "^1.11" }, "type": "symfony-bundle", "extra": { @@ -12002,7 +11602,7 @@ "turbo-stream" ], "support": { - "source": "https://github.com/symfony/ux-turbo/tree/v2.8.1" + "source": "https://github.com/symfony/ux-turbo/tree/v2.9.1" }, "funding": [ { @@ -12018,75 +11618,59 @@ "type": "tidelift" } ], - "time": "2023-04-11T18:15:46+00:00" + "time": "2023-05-30T14:40:50+00:00" }, { "name": "symfony/validator", - "version": "v5.4.24", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "47794a3cb530e01593ecad9856ba80f5c011e36b" + "reference": "1b71f43c62ee867ab08195ba6039a1bc3e6654dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/47794a3cb530e01593ecad9856ba80f5c011e36b", - "reference": "47794a3cb530e01593ecad9856ba80f5c011e36b", + "url": "https://api.github.com/repos/symfony/validator/zipball/1b71f43c62ee867ab08195ba6039a1bc3e6654dc", + "reference": "1b71f43c62ee867ab08195ba6039a1bc3e6654dc", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "~1.0", - "symfony/polyfill-php80": "^1.16", - "symfony/polyfill-php81": "^1.22", - "symfony/translation-contracts": "^1.1|^2|^3" + "symfony/polyfill-php83": "^1.27", + "symfony/translation-contracts": "^2.5|^3" }, "conflict": { "doctrine/annotations": "<1.13", - "doctrine/cache": "<1.11", "doctrine/lexer": "<1.1", - "symfony/dependency-injection": "<4.4", - "symfony/expression-language": "<5.1", - "symfony/http-kernel": "<4.4", - "symfony/intl": "<4.4", - "symfony/property-info": "<5.3", - "symfony/translation": "<4.4", - "symfony/yaml": "<4.4" + "symfony/dependency-injection": "<5.4", + "symfony/expression-language": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/intl": "<5.4", + "symfony/property-info": "<5.4", + "symfony/translation": "<5.4", + "symfony/yaml": "<5.4" }, "require-dev": { "doctrine/annotations": "^1.13|^2", - "doctrine/cache": "^1.11|^2.0", "egulias/email-validator": "^2.1.10|^3|^4", - "symfony/cache": "^4.4|^5.0|^6.0", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^5.1|^6.0", - "symfony/finder": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/http-foundation": "^4.4|^5.0|^6.0", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/intl": "^4.4|^5.0|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/property-access": "^4.4|^5.0|^6.0", - "symfony/property-info": "^5.3|^6.0", - "symfony/translation": "^4.4|^5.0|^6.0", - "symfony/yaml": "^4.4|^5.0|^6.0" - }, - "suggest": { - "egulias/email-validator": "Strict (RFC compliant) email validation", - "psr/cache-implementation": "For using the mapping cache.", - "symfony/config": "", - "symfony/expression-language": "For using the Expression validator and the ExpressionLanguageSyntax constraints", - "symfony/http-foundation": "", - "symfony/intl": "", - "symfony/property-access": "For accessing properties within comparison constraints", - "symfony/property-info": "To automatically add NotNull and Type constraints", - "symfony/translation": "For translating validation errors.", - "symfony/yaml": "" + "symfony/cache": "^5.4|^6.0", + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -12114,7 +11698,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v5.4.24" + "source": "https://github.com/symfony/validator/tree/v6.3.1" }, "funding": [ { @@ -12130,42 +11714,36 @@ "type": "tidelift" } ], - "time": "2023-05-25T13:05:00+00:00" + "time": "2023-06-21T12:08:28+00:00" }, { "name": "symfony/var-dumper", - "version": "v5.4.24", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "8e12706bf9c68a2da633f23bfdc15b4dce5970b3" + "reference": "c81268d6960ddb47af17391a27d222bd58cf0515" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/8e12706bf9c68a2da633f23bfdc15b4dce5970b3", - "reference": "8e12706bf9c68a2da633f23bfdc15b4dce5970b3", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c81268d6960ddb47af17391a27d222bd58cf0515", + "reference": "c81268d6960ddb47af17391a27d222bd58cf0515", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/console": "<4.4" + "symfony/console": "<5.4" }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/uid": "^5.1|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", "twig/twig": "^2.13|^3.0.4" }, - "suggest": { - "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", - "ext-intl": "To show region name in time zone dump", - "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" - }, "bin": [ "Resources/bin/var-dump-server" ], @@ -12202,7 +11780,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v5.4.24" + "source": "https://github.com/symfony/var-dumper/tree/v6.3.1" }, "funding": [ { @@ -12218,28 +11796,27 @@ "type": "tidelift" } ], - "time": "2023-05-25T13:05:00+00:00" + "time": "2023-06-21T12:08:28+00:00" }, { "name": "symfony/var-exporter", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "be74908a6942fdd331554b3cec27ff41b45ccad4" + "reference": "db5416d04269f2827d8c54331ba4cfa42620d350" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/be74908a6942fdd331554b3cec27ff41b45ccad4", - "reference": "be74908a6942fdd331554b3cec27ff41b45ccad4", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/db5416d04269f2827d8c54331ba4cfa42620d350", + "reference": "db5416d04269f2827d8c54331ba4cfa42620d350", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1" }, "require-dev": { - "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0" + "symfony/var-dumper": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -12272,10 +11849,12 @@ "export", "hydrate", "instantiate", + "lazy-loading", + "proxy", "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v5.4.21" + "source": "https://github.com/symfony/var-exporter/tree/v6.3.0" }, "funding": [ { @@ -12291,38 +11870,34 @@ "type": "tidelift" } ], - "time": "2023-02-21T19:46:44+00:00" + "time": "2023-04-21T08:48:44+00:00" }, { "name": "symfony/web-link", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/web-link.git", - "reference": "57c03a5e89ed7c2d7a1a09258dfec12f95f95adb" + "reference": "0989ca617d0703cdca501a245f10e194ff22315b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-link/zipball/57c03a5e89ed7c2d7a1a09258dfec12f95f95adb", - "reference": "57c03a5e89ed7c2d7a1a09258dfec12f95f95adb", + "url": "https://api.github.com/repos/symfony/web-link/zipball/0989ca617d0703cdca501a245f10e194ff22315b", + "reference": "0989ca617d0703cdca501a245f10e194ff22315b", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/link": "^1.0", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "psr/link": "^1.1|^2.0" }, "conflict": { - "symfony/http-kernel": "<5.3" + "symfony/http-kernel": "<5.4" }, "provide": { - "psr/link-implementation": "1.0" + "psr/link-implementation": "1.0|2.0" }, "require-dev": { - "symfony/http-kernel": "^5.3|^6.0" - }, - "suggest": { - "symfony/http-kernel": "" + "symfony/http-kernel": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -12362,7 +11937,7 @@ "push" ], "support": { - "source": "https://github.com/symfony/web-link/tree/v5.4.21" + "source": "https://github.com/symfony/web-link/tree/v6.3.0" }, "funding": [ { @@ -12378,37 +11953,35 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-04-21T14:41:17+00:00" }, { "name": "symfony/webpack-encore-bundle", - "version": "v1.17.1", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/symfony/webpack-encore-bundle.git", - "reference": "7e3b6f69bcfcbb40ecfe83ad7a77e44316d26573" + "reference": "150fe022740fef908f4ca3d5950ce85ab040ec76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/7e3b6f69bcfcbb40ecfe83ad7a77e44316d26573", - "reference": "7e3b6f69bcfcbb40ecfe83ad7a77e44316d26573", + "url": "https://api.github.com/repos/symfony/webpack-encore-bundle/zipball/150fe022740fef908f4ca3d5950ce85ab040ec76", + "reference": "150fe022740fef908f4ca3d5950ce85ab040ec76", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/asset": "^4.4 || ^5.0 || ^6.0", - "symfony/config": "^4.4 || ^5.0 || ^6.0", - "symfony/dependency-injection": "^4.4 || ^5.0 || ^6.0", - "symfony/deprecation-contracts": "^2.1 || ^3.0", - "symfony/http-kernel": "^4.4 || ^5.0 || ^6.0", - "symfony/polyfill-php80": "^1.25.0", - "symfony/service-contracts": "^1.0 || ^2.0 || ^3.0" + "php": ">=8.1.0", + "symfony/asset": "^5.4 || ^6.2", + "symfony/config": "^5.4 || ^6.2", + "symfony/dependency-injection": "^5.4 || ^6.2", + "symfony/http-kernel": "^5.4 || ^6.2", + "symfony/service-contracts": "^1.1.9 || ^2.1.3 || ^3.0" }, "require-dev": { - "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0", - "symfony/phpunit-bridge": "^5.3 || ^6.0", - "symfony/twig-bundle": "^4.4 || ^5.0 || ^6.0", - "symfony/web-link": "^4.4 || ^5.0 || ^6.0" + "symfony/framework-bundle": "^5.4 || ^6.2", + "symfony/phpunit-bridge": "^5.4 || ^6.2", + "symfony/twig-bundle": "^5.4 || ^6.2", + "symfony/web-link": "^5.4 || ^6.2" }, "type": "symfony-bundle", "extra": { @@ -12435,7 +12008,7 @@ "description": "Integration with your Symfony app & Webpack Encore!", "support": { "issues": "https://github.com/symfony/webpack-encore-bundle/issues", - "source": "https://github.com/symfony/webpack-encore-bundle/tree/v1.17.1" + "source": "https://github.com/symfony/webpack-encore-bundle/tree/v2.0.1" }, "funding": [ { @@ -12451,35 +12024,31 @@ "type": "tidelift" } ], - "time": "2023-05-29T00:18:01+00:00" + "time": "2023-05-31T14:28:33+00:00" }, { "name": "symfony/yaml", - "version": "v5.4.23", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "4cd2e3ea301aadd76a4172756296fe552fb45b0b" + "reference": "a9a8337aa641ef2aa39c3e028f9107ec391e5927" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/4cd2e3ea301aadd76a4172756296fe552fb45b0b", - "reference": "4cd2e3ea301aadd76a4172756296fe552fb45b0b", + "url": "https://api.github.com/repos/symfony/yaml/zipball/a9a8337aa641ef2aa39c3e028f9107ec391e5927", + "reference": "a9a8337aa641ef2aa39c3e028f9107ec391e5927", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "php": ">=8.1", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.3" + "symfony/console": "<5.4" }, "require-dev": { - "symfony/console": "^5.3|^6.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" + "symfony/console": "^5.4|^6.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -12510,7 +12079,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.23" + "source": "https://github.com/symfony/yaml/tree/v6.3.0" }, "funding": [ { @@ -12526,7 +12095,7 @@ "type": "tidelift" } ], - "time": "2023-04-23T19:33:36+00:00" + "time": "2023-04-28T13:28:14+00:00" }, { "name": "tecnickcom/tc-lib-barcode", @@ -12697,145 +12266,6 @@ ], "time": "2023-05-18T08:09:02+00:00" }, - { - "name": "thecodingmachine/safe", - "version": "v1.3.3", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/safe.git", - "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/a8ab0876305a4cdaef31b2350fcb9811b5608dbc", - "reference": "a8ab0876305a4cdaef31b2350fcb9811b5608dbc", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "require-dev": { - "phpstan/phpstan": "^0.12", - "squizlabs/php_codesniffer": "^3.2", - "thecodingmachine/phpstan-strict-rules": "^0.12" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.1-dev" - } - }, - "autoload": { - "files": [ - "deprecated/apc.php", - "deprecated/libevent.php", - "deprecated/mssql.php", - "deprecated/stats.php", - "lib/special_cases.php", - "generated/apache.php", - "generated/apcu.php", - "generated/array.php", - "generated/bzip2.php", - "generated/calendar.php", - "generated/classobj.php", - "generated/com.php", - "generated/cubrid.php", - "generated/curl.php", - "generated/datetime.php", - "generated/dir.php", - "generated/eio.php", - "generated/errorfunc.php", - "generated/exec.php", - "generated/fileinfo.php", - "generated/filesystem.php", - "generated/filter.php", - "generated/fpm.php", - "generated/ftp.php", - "generated/funchand.php", - "generated/gmp.php", - "generated/gnupg.php", - "generated/hash.php", - "generated/ibase.php", - "generated/ibmDb2.php", - "generated/iconv.php", - "generated/image.php", - "generated/imap.php", - "generated/info.php", - "generated/ingres-ii.php", - "generated/inotify.php", - "generated/json.php", - "generated/ldap.php", - "generated/libxml.php", - "generated/lzf.php", - "generated/mailparse.php", - "generated/mbstring.php", - "generated/misc.php", - "generated/msql.php", - "generated/mysql.php", - "generated/mysqli.php", - "generated/mysqlndMs.php", - "generated/mysqlndQc.php", - "generated/network.php", - "generated/oci8.php", - "generated/opcache.php", - "generated/openssl.php", - "generated/outcontrol.php", - "generated/password.php", - "generated/pcntl.php", - "generated/pcre.php", - "generated/pdf.php", - "generated/pgsql.php", - "generated/posix.php", - "generated/ps.php", - "generated/pspell.php", - "generated/readline.php", - "generated/rpminfo.php", - "generated/rrd.php", - "generated/sem.php", - "generated/session.php", - "generated/shmop.php", - "generated/simplexml.php", - "generated/sockets.php", - "generated/sodium.php", - "generated/solr.php", - "generated/spl.php", - "generated/sqlsrv.php", - "generated/ssdeep.php", - "generated/ssh2.php", - "generated/stream.php", - "generated/strings.php", - "generated/swoole.php", - "generated/uodbc.php", - "generated/uopz.php", - "generated/url.php", - "generated/var.php", - "generated/xdiff.php", - "generated/xml.php", - "generated/xmlrpc.php", - "generated/yaml.php", - "generated/yaz.php", - "generated/zip.php", - "generated/zlib.php" - ], - "psr-4": { - "Safe\\": [ - "lib/", - "deprecated/", - "generated/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHP core functions that throw exceptions instead of returning FALSE on error", - "support": { - "issues": "https://github.com/thecodingmachine/safe/issues", - "source": "https://github.com/thecodingmachine/safe/tree/v1.3.3" - }, - "time": "2020-10-28T17:51:34+00:00" - }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "2.2.6", @@ -13426,25 +12856,43 @@ }, { "name": "web-auth/cose-lib", - "version": "v3.3.12", + "version": "4.2.0", "source": { "type": "git", "url": "https://github.com/web-auth/cose-lib.git", - "reference": "efa6ec2ba4e840bc1316a493973c9916028afeeb" + "reference": "9e0cc49810b445ed0aa50d122ee82c63882af9a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/efa6ec2ba4e840bc1316a493973c9916028afeeb", - "reference": "efa6ec2ba4e840bc1316a493973c9916028afeeb", + "url": "https://api.github.com/repos/web-auth/cose-lib/zipball/9e0cc49810b445ed0aa50d122ee82c63882af9a0", + "reference": "9e0cc49810b445ed0aa50d122ee82c63882af9a0", "shasum": "" }, "require": { - "beberlei/assert": "^3.2", + "brick/math": "^0.9|^0.10|^0.11", "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "fgrosse/phpasn1": "^2.1", - "php": ">=7.2" + "php": ">=8.1", + "spomky-labs/pki-framework": "^1.0" + }, + "require-dev": { + "ekino/phpstan-banned-code": "^1.0", + "infection/infection": "^0.26.12", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/phpstan": "^1.7", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.2", + "phpunit/phpunit": "^10.0", + "qossmic/deptrac-shim": "^1.0", + "rector/rector": "^0.15", + "symfony/phpunit-bridge": "^6.1", + "symplify/easy-coding-standard": "^11.0" + }, + "suggest": { + "ext-bcmath": "For better performance, please install either GMP (recommended) or BCMath extension", + "ext-gmp": "For better performance, please install either GMP (recommended) or BCMath extension" }, "type": "library", "autoload": { @@ -13473,7 +12921,8 @@ "RFC8152" ], "support": { - "source": "https://github.com/web-auth/cose-lib/tree/v3.3.12" + "issues": "https://github.com/web-auth/cose-lib/issues", + "source": "https://github.com/web-auth/cose-lib/tree/4.2.0" }, "funding": [ { @@ -13485,36 +12934,48 @@ "type": "patreon" } ], - "time": "2021-12-04T12:13:35+00:00" + "time": "2023-02-28T18:05:22+00:00" }, { "name": "web-auth/metadata-service", - "version": "v3.3.12", + "version": "4.6.2", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-metadata-service.git", - "reference": "ef40d2b7b68c4964247d13fab52e2fa8dbd65246" + "reference": "b58fbb0df46450acc426329bd87b60d794859da0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/ef40d2b7b68c4964247d13fab52e2fa8dbd65246", - "reference": "ef40d2b7b68c4964247d13fab52e2fa8dbd65246", + "url": "https://api.github.com/repos/web-auth/webauthn-metadata-service/zipball/b58fbb0df46450acc426329bd87b60d794859da0", + "reference": "b58fbb0df46450acc426329bd87b60d794859da0", "shasum": "" }, "require": { - "beberlei/assert": "^3.2", "ext-json": "*", - "league/uri": "^6.0", - "php": ">=7.2", + "lcobucci/clock": "^2.2|^3.0", + "paragonie/constant_time_encoding": "^2.6", + "php": ">=8.1", + "psr/clock": "^1.0", + "psr/event-dispatcher": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "psr/log": "^1.1" + "psr/log": "^1.0|^2.0|^3.0", + "spomky-labs/pki-framework": "^1.0", + "symfony/deprecation-contracts": "^3.2" }, "suggest": { + "psr/clock-implementation": "As of 4.5.x, the PSR Clock implementation will replace lcobucci/clock", + "psr/log-implementation": "Recommended to receive logs from the library", "web-token/jwt-key-mgmt": "Mandatory for fetching Metadata Statement from distant sources", "web-token/jwt-signature-algorithm-ecdsa": "Mandatory for fetching Metadata Statement from distant sources" }, "type": "library", + "extra": { + "thanks": { + "name": "web-auth/webauthn-framework", + "url": "https://github.com/web-auth/webauthn-framework" + } + }, "autoload": { "psr-4": { "Webauthn\\MetadataService\\": "src/" @@ -13542,7 +13003,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-metadata-service/tree/v3.3.12" + "source": "https://github.com/web-auth/webauthn-metadata-service/tree/4.6.2" }, "funding": [ { @@ -13554,49 +13015,55 @@ "type": "patreon" } ], - "time": "2021-11-21T11:14:31+00:00" + "time": "2023-06-01T19:06:30+00:00" }, { "name": "web-auth/webauthn-lib", - "version": "v3.3.12", + "version": "4.6.2", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-lib.git", - "reference": "5ef9b21c8e9f8a817e524ac93290d08a9f065b33" + "reference": "e0f85f09b4e1a48169352290e7ccfd29ade93e34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/5ef9b21c8e9f8a817e524ac93290d08a9f065b33", - "reference": "5ef9b21c8e9f8a817e524ac93290d08a9f065b33", + "url": "https://api.github.com/repos/web-auth/webauthn-lib/zipball/e0f85f09b4e1a48169352290e7ccfd29ade93e34", + "reference": "e0f85f09b4e1a48169352290e7ccfd29ade93e34", "shasum": "" }, "require": { - "beberlei/assert": "^3.2", "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "fgrosse/phpasn1": "^2.1", - "php": ">=7.2", + "paragonie/constant_time_encoding": "^2.6", + "php": ">=8.1", + "psr/event-dispatcher": "^1.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "psr/log": "^1.1", - "ramsey/uuid": "^3.8|^4.0", - "spomky-labs/base64url": "^2.0", - "spomky-labs/cbor-php": "^1.0|^2.0", - "symfony/process": "^3.0|^4.0|^5.0", - "thecodingmachine/safe": "^1.1", - "web-auth/cose-lib": "self.version", + "psr/log": "^1.0|^2.0|^3.0", + "spomky-labs/cbor-php": "^3.0", + "symfony/uid": "^6.1", + "web-auth/cose-lib": "^4.0.12", "web-auth/metadata-service": "self.version" }, + "require-dev": { + "symfony/event-dispatcher": "^6.1" + }, "suggest": { "psr/log-implementation": "Recommended to receive logs from the library", + "symfony/event-dispatcher": "Recommended to use dispatched events", "web-token/jwt-key-mgmt": "Mandatory for the AndroidSafetyNet Attestation Statement support", "web-token/jwt-signature-algorithm-ecdsa": "Recommended for the AndroidSafetyNet Attestation Statement support", "web-token/jwt-signature-algorithm-eddsa": "Recommended for the AndroidSafetyNet Attestation Statement support", "web-token/jwt-signature-algorithm-rsa": "Mandatory for the AndroidSafetyNet Attestation Statement support" }, "type": "library", + "extra": { + "thanks": { + "name": "web-auth/webauthn-framework", + "url": "https://github.com/web-auth/webauthn-framework" + } + }, "autoload": { "psr-4": { "Webauthn\\": "src/" @@ -13624,7 +13091,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-lib/tree/v3.3.12" + "source": "https://github.com/web-auth/webauthn-lib/tree/4.6.2" }, "funding": [ { @@ -13636,31 +13103,47 @@ "type": "patreon" } ], - "time": "2022-02-18T07:13:44+00:00" + "time": "2023-06-12T14:32:32+00:00" }, { "name": "web-auth/webauthn-symfony-bundle", - "version": "v3.3.12", + "version": "4.6.2", "source": { "type": "git", "url": "https://github.com/web-auth/webauthn-symfony-bundle.git", - "reference": "15f2091dc351f190d27a377a0dbbc117e6be5329" + "reference": "04bd26182e26c8bf218bdca3d8b0f569ff983ff8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/15f2091dc351f190d27a377a0dbbc117e6be5329", - "reference": "15f2091dc351f190d27a377a0dbbc117e6be5329", + "url": "https://api.github.com/repos/web-auth/webauthn-symfony-bundle/zipball/04bd26182e26c8bf218bdca3d8b0f569ff983ff8", + "reference": "04bd26182e26c8bf218bdca3d8b0f569ff983ff8", "shasum": "" }, "require": { - "spomky-labs/cbor-bundle": "^2.0", - "symfony/config": "^4.4|^5.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/framework-bundle": "^4.4|^5.0", + "nyholm/psr7": "^1.5", + "php": ">=8.1", + "psr/event-dispatcher": "^1.0", + "spomky-labs/cbor-bundle": "^3.0", + "symfony/config": "^6.1", + "symfony/dependency-injection": "^6.1", + "symfony/framework-bundle": "^6.1", + "symfony/http-client": "^6.1", + "symfony/psr-http-message-bridge": "^2.1", + "symfony/security-bundle": "^6.1", + "symfony/security-core": "^6.1", + "symfony/security-http": "^6.1", + "symfony/serializer": "^6.1", + "symfony/validator": "^6.1", "web-auth/webauthn-lib": "self.version", - "web-token/jwt-signature": "^2.0.9" + "web-token/jwt-signature": "^3.1" }, "type": "symfony-bundle", + "extra": { + "thanks": { + "name": "web-auth/webauthn-framework", + "url": "https://github.com/web-auth/webauthn-framework" + } + }, "autoload": { "psr-4": { "Webauthn\\Bundle\\": "src/" @@ -13688,7 +13171,7 @@ "webauthn" ], "support": { - "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/v3.3.12" + "source": "https://github.com/web-auth/webauthn-symfony-bundle/tree/4.6.2" }, "funding": [ { @@ -13700,29 +13183,29 @@ "type": "patreon" } ], - "time": "2021-11-21T11:14:31+00:00" + "time": "2023-06-12T14:32:32+00:00" }, { "name": "web-token/jwt-core", - "version": "v2.2.11", + "version": "3.2.7", "source": { "type": "git", "url": "https://github.com/web-token/jwt-core.git", - "reference": "53beb6f6c1eec4fa93c1c3e5d9e5701e71fa1678" + "reference": "db58b6ebbe1a7d5869688e989b1cf110c6ab888f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-core/zipball/53beb6f6c1eec4fa93c1c3e5d9e5701e71fa1678", - "reference": "53beb6f6c1eec4fa93c1c3e5d9e5701e71fa1678", + "url": "https://api.github.com/repos/web-token/jwt-core/zipball/db58b6ebbe1a7d5869688e989b1cf110c6ab888f", + "reference": "db58b6ebbe1a7d5869688e989b1cf110c6ab888f", "shasum": "" }, "require": { - "brick/math": "^0.8.17|^0.9", + "brick/math": "^0.9|^0.10|^0.11", "ext-json": "*", "ext-mbstring": "*", - "fgrosse/phpasn1": "^2.0", - "php": ">=7.2", - "spomky-labs/base64url": "^1.0|^2.0" + "paragonie/constant_time_encoding": "^2.4", + "php": ">=8.1", + "spomky-labs/pki-framework": "^1.0" }, "conflict": { "spomky-labs/jose": "*" @@ -13768,7 +13251,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-core/tree/v2.2.11" + "source": "https://github.com/web-token/jwt-core/tree/3.2.7" }, "funding": [ { @@ -13776,24 +13259,25 @@ "type": "patreon" } ], - "time": "2021-03-17T14:55:52+00:00" + "time": "2023-02-02T17:35:17+00:00" }, { "name": "web-token/jwt-signature", - "version": "v2.2.11", + "version": "3.2.7", "source": { "type": "git", "url": "https://github.com/web-token/jwt-signature.git", - "reference": "015b59aaf3b6e8fb9f5bd1338845b7464c7d8103" + "reference": "156e0b0ef534e53eecf23a32a92ee6d8cb4fdac4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/web-token/jwt-signature/zipball/015b59aaf3b6e8fb9f5bd1338845b7464c7d8103", - "reference": "015b59aaf3b6e8fb9f5bd1338845b7464c7d8103", + "url": "https://api.github.com/repos/web-token/jwt-signature/zipball/156e0b0ef534e53eecf23a32a92ee6d8cb4fdac4", + "reference": "156e0b0ef534e53eecf23a32a92ee6d8cb4fdac4", "shasum": "" }, "require": { - "web-token/jwt-core": "^2.1" + "php": ">=8.1", + "web-token/jwt-core": "^3.2" }, "suggest": { "web-token/jwt-signature-algorithm-ecdsa": "ECDSA Based Signature Algorithms", @@ -13844,7 +13328,7 @@ "symfony" ], "support": { - "source": "https://github.com/web-token/jwt-signature/tree/v2.2.11" + "source": "https://github.com/web-token/jwt-signature/tree/3.2.7" }, "funding": [ { @@ -13852,7 +13336,7 @@ "type": "patreon" } ], - "time": "2021-03-01T19:55:28+00:00" + "time": "2023-05-18T16:20:51+00:00" }, { "name": "webmozart/assert", @@ -14809,16 +14293,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.18", + "version": "1.10.21", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "52b6416c579663eebdd2f1d97df21971daf3b43f" + "reference": "b2a30186be2e4d97dce754ae4e65eb0ec2f04eb5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52b6416c579663eebdd2f1d97df21971daf3b43f", - "reference": "52b6416c579663eebdd2f1d97df21971daf3b43f", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b2a30186be2e4d97dce754ae4e65eb0ec2f04eb5", + "reference": "b2a30186be2e4d97dce754ae4e65eb0ec2f04eb5", "shasum": "" }, "require": { @@ -14867,7 +14351,7 @@ "type": "tidelift" } ], - "time": "2023-06-07T22:00:43+00:00" + "time": "2023-06-21T20:07:58+00:00" }, { "name": "phpstan/phpstan-doctrine", @@ -14939,6 +14423,55 @@ }, "time": "2023-05-11T11:26:04+00:00" }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/b21c03d4f6f3a446e4311155f4be9d65048218e6", + "reference": "b21c03d4f6f3a446e4311155f4be9d65048218e6", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.10" + }, + "require-dev": { + "nikic/php-parser": "^4.13.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.5.1" + }, + "time": "2023-03-29T14:47:40+00:00" + }, { "name": "phpstan/phpstan-symfony", "version": "1.3.2", @@ -15075,23 +14608,84 @@ }, "time": "2023-04-21T15:40:12+00:00" }, + { + "name": "rector/rector", + "version": "0.17.1", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "11401dc1abba0a359fabbf98f1057f4e65129f86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/11401dc1abba0a359fabbf98f1057f4e65129f86", + "reference": "11401dc1abba0a359fabbf98f1057f4e65129f86", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "phpstan/phpstan": "^1.10.15" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.15-dev" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/0.17.1" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2023-06-14T09:05:33+00:00" + }, { "name": "roave/security-advisories", "version": "dev-latest", "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "22b763b5abdc69572b66c462cd85c2b2135f56aa" + "reference": "237f1821ece806de66072813d8cbe5bdbc8f3117" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/22b763b5abdc69572b66c462cd85c2b2135f56aa", - "reference": "22b763b5abdc69572b66c462cd85c2b2135f56aa", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/237f1821ece806de66072813d8cbe5bdbc8f3117", + "reference": "237f1821ece806de66072813d8cbe5bdbc8f3117", "shasum": "" }, "conflict": { "3f/pygmentize": "<1.2", - "admidio/admidio": "<4.1.9", + "admidio/admidio": "<4.2.8", "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", "aheinze/cockpit": "<=2.2.1", "akaunting/akaunting": "<2.1.13", @@ -15104,7 +14698,7 @@ "amphp/http-client": ">=4,<4.4", "anchorcms/anchor-cms": "<=0.12.7", "andreapollastri/cipi": "<=3.1.15", - "andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<=1.0.1|>=2,<=2.2.4", + "andrewhaine/silverstripe-form-capture": ">=0.2,<=0.2.3|>=1,<1.0.2|>=2,<2.2.5", "apereo/phpcas": "<1.6", "api-platform/core": ">=2.2,<2.2.10|>=2.3,<2.3.6|>=2.6,<2.7.10|>=3,<3.0.12|>=3.1,<3.1.3", "appwrite/server-ce": "<=1.2.1", @@ -15124,7 +14718,7 @@ "barzahlen/barzahlen-php": "<2.0.1", "baserproject/basercms": "<4.7.5", "bassjobsen/bootstrap-3-typeahead": ">4.0.2", - "bigfork/silverstripe-form-capture": ">=3,<=3.1", + "bigfork/silverstripe-form-capture": ">=3,<3.1.1", "billz/raspap-webgui": "<=2.6.6", "bk2k/bootstrap-package": ">=7.1,<7.1.2|>=8,<8.0.8|>=9,<9.0.4|>=9.1,<9.1.3|>=10,<10.0.10|>=11,<11.0.3", "bmarshall511/wordpress_zero_spam": "<5.2.13", @@ -15162,7 +14756,7 @@ "contao/core-bundle": "<4.9.40|>=4.10,<4.11.7|>=4.13,<4.13.21|>=5.1,<5.1.4|= 4.10.0", "contao/listing-bundle": ">=4,<4.4.8", "contao/managed-edition": "<=1.5", - "craftcms/cms": ">= 4.0.0-RC1, < 4.4.12|>= 4.0.0-RC1, <= 4.4.5|>= 4.0.0-RC1, <= 4.4.6|<=3.8.5|>=4,<4.4.6|>= 4.0.0-RC1, < 4.4.6|>= 4.0.0-RC1, < 4.3.7|>= 4.0.0-RC1, < 4.2.1", + "craftcms/cms": "<=4.4.9|>= 4.0.0-RC1, < 4.4.12|>= 4.0.0-RC1, <= 4.4.5|>= 4.0.0-RC1, <= 4.4.6|>= 4.0.0-RC1, < 4.4.6|>= 4.0.0-RC1, < 4.3.7|>= 4.0.0-RC1, < 4.2.1", "croogo/croogo": "<3.0.7", "cuyz/valinor": "<0.12", "czproject/git-php": "<4.0.3", @@ -15242,12 +14836,12 @@ "friendsofsymfony/user-bundle": ">=1.2,<1.3.5", "friendsoftypo3/mediace": ">=7.6.2,<7.6.5", "froala/wysiwyg-editor": "<3.2.7", - "froxlor/froxlor": "<2.0.20", + "froxlor/froxlor": "<2.1", "fuel/core": "<1.8.1", - "funadmin/funadmin": "<=3.2", + "funadmin/funadmin": "<=3.2|>=3.3.2,<=3.3.3", "gaoming13/wechat-php-sdk": "<=1.10.2", "genix/cms": "<=1.1.11", - "getgrav/grav": "<1.7.34", + "getgrav/grav": "<1.7.42", "getkirby/cms": "= 3.8.0|<3.5.8.2|>=3.6,<3.6.6.2|>=3.7,<3.7.5.1", "getkirby/panel": "<2.5.14", "getkirby/starterkit": "<=3.7.0.2", @@ -15262,6 +14856,7 @@ "guzzlehttp/psr7": "<1.9.1|>=2,<2.4.5", "harvesthq/chosen": "<1.8.7", "helloxz/imgurl": "= 2.31|<=2.31", + "hhxsv5/laravel-s": "<3.7.36", "hillelcoren/invoice-ninja": "<5.3.35", "himiklab/yii2-jqgrid-widget": "<1.0.8", "hjue/justwriting": "<=1", @@ -15282,6 +14877,7 @@ "illuminate/view": "<6.20.42|>=7,<7.30.6|>=8,<8.75", "impresscms/impresscms": "<=1.4.3", "in2code/femanager": "<5.5.3|>=6,<6.3.4|>=7,<7.1", + "in2code/ipandlanguageredirect": "<5.1.2", "in2code/lux": "<17.6.1|>=18,<24.0.2", "innologi/typo3-appointments": "<2.0.6", "intelliants/subrion": "<=4.2.1", @@ -15301,6 +14897,7 @@ "kazist/phpwhois": "<=4.2.6", "kelvinmo/simplexrd": "<3.1.1", "kevinpapst/kimai2": "<1.16.7", + "khodakhah/nodcms": "<=3", "kimai/kimai": "<1.1", "kitodo/presentation": "<3.1.2", "klaviyo/magento2-extension": ">=1,<3", @@ -15455,6 +15052,7 @@ "scheb/two-factor-bundle": ">=0,<3.26|>=4,<4.11", "sensiolabs/connect": "<4.2.3", "serluck/phpwhois": "<=4.2.6", + "sheng/yiicms": "<=1.2", "shopware/core": "<=6.4.20", "shopware/platform": "<=6.4.20", "shopware/production": "<=6.3.5.2", @@ -15498,7 +15096,7 @@ "ssddanbrown/bookstack": "<22.2.3", "statamic/cms": "<3.2.39|>=3.3,<3.3.2", "stormpath/sdk": ">=0,<9.9.99", - "studio-42/elfinder": "<2.1.59", + "studio-42/elfinder": "<2.1.62", "subrion/cms": "<=4.2.1", "sukohi/surpass": "<1", "sulu/sulu": "= 2.4.0-RC1|<1.6.44|>=2,<2.2.18|>=2.3,<2.3.8", @@ -15562,6 +15160,7 @@ "topthink/framework": "<6.0.14", "topthink/think": "<=6.1.1", "topthink/thinkphp": "<=3.2.3", + "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", "tribalsystems/zenario": "<=9.3.57595", "truckersmp/phpwhois": "<=4.3.1", "ttskch/pagination-service-provider": "<1", @@ -15591,6 +15190,8 @@ "web-auth/webauthn-framework": ">=3.3,<3.3.4", "webbuilders-group/silverstripe-kapost-bridge": "<0.4", "webcoast/deferred-image-processing": "<1.0.2", + "webklex/laravel-imap": "<5.3", + "webklex/php-imap": "<5.3", "webpa/webpa": "<3.1.2", "wikimedia/parsoid": "<0.12.2", "willdurand/js-translation-bundle": "<2.1.1", @@ -15682,33 +15283,33 @@ "type": "tidelift" } ], - "time": "2023-06-09T23:04:09+00:00" + "time": "2023-06-22T20:04:46+00:00" }, { "name": "sebastian/diff", - "version": "4.0.5", + "version": "5.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/912dc2fbe3e3c1e7873313cc801b100b6c68c87b", + "reference": "912dc2fbe3e3c1e7873313cc801b100b6c68c87b", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^10.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -15740,7 +15341,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.0.3" }, "funding": [ { @@ -15748,30 +15350,29 @@ "type": "github" } ], - "time": "2023-05-07T05:35:17+00:00" + "time": "2023-05-01T07:48:21+00:00" }, { "name": "spatie/array-to-xml", - "version": "2.17.1", + "version": "3.1.6", "source": { "type": "git", "url": "https://github.com/spatie/array-to-xml.git", - "reference": "5cbec9c6ab17e320c58a259f0cebe88bde4a7c46" + "reference": "e210b98957987c755372465be105d32113f339a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/5cbec9c6ab17e320c58a259f0cebe88bde4a7c46", - "reference": "5cbec9c6ab17e320c58a259f0cebe88bde4a7c46", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/e210b98957987c755372465be105d32113f339a4", + "reference": "e210b98957987c755372465be105d32113f339a4", "shasum": "" }, "require": { "ext-dom": "*", - "php": "^7.4|^8.0" + "php": "^8.0" }, "require-dev": { "mockery/mockery": "^1.2", "pestphp/pest": "^1.21", - "phpunit/phpunit": "^9.0", "spatie/pest-plugin-snapshots": "^1.1" }, "type": "library", @@ -15800,7 +15401,7 @@ "xml" ], "support": { - "source": "https://github.com/spatie/array-to-xml/tree/2.17.1" + "source": "https://github.com/spatie/array-to-xml/tree/3.1.6" }, "funding": [ { @@ -15812,35 +15413,31 @@ "type": "github" } ], - "time": "2022-12-26T08:22:07+00:00" + "time": "2023-05-11T14:04:07+00:00" }, { "name": "symfony/browser-kit", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "a866ca7e396f15d7efb6d74a8a7d364d4e05b704" + "reference": "0eb7228e7c435169e65c911ba8d107d56d850049" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/a866ca7e396f15d7efb6d74a8a7d364d4e05b704", - "reference": "a866ca7e396f15d7efb6d74a8a7d364d4e05b704", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/0eb7228e7c435169e65c911ba8d107d56d850049", + "reference": "0eb7228e7c435169e65c911ba8d107d56d850049", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/dom-crawler": "^4.4|^5.0|^6.0", - "symfony/polyfill-php80": "^1.16" + "php": ">=8.1", + "symfony/dom-crawler": "^5.4|^6.0" }, "require-dev": { - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/mime": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/process": "" + "symfony/css-selector": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -15868,7 +15465,7 @@ "description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/browser-kit/tree/v5.4.21" + "source": "https://github.com/symfony/browser-kit/tree/v6.3.0" }, "funding": [ { @@ -15884,42 +15481,37 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-04-25T10:46:17+00:00" }, { "name": "symfony/debug-bundle", - "version": "v5.4.21", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/symfony/debug-bundle.git", - "reference": "8b4360bf8ce9a917ef8796c5e6065a185d8722bd" + "reference": "02fe831f7cdd472c561116189bcc30d0759665e7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/8b4360bf8ce9a917ef8796c5e6065a185d8722bd", - "reference": "8b4360bf8ce9a917ef8796c5e6065a185d8722bd", + "url": "https://api.github.com/repos/symfony/debug-bundle/zipball/02fe831f7cdd472c561116189bcc30d0759665e7", + "reference": "02fe831f7cdd472c561116189bcc30d0759665e7", "shasum": "" }, "require": { "ext-xml": "*", - "php": ">=7.2.5", - "symfony/http-kernel": "^4.4|^5.0|^6.0", - "symfony/polyfill-php80": "^1.16", - "symfony/twig-bridge": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" + "php": ">=8.1", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/twig-bridge": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" }, "conflict": { - "symfony/config": "<4.4", - "symfony/dependency-injection": "<5.2" + "symfony/config": "<5.4", + "symfony/dependency-injection": "<5.4" }, "require-dev": { - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/web-profiler-bundle": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/config": "For service container configuration", - "symfony/dependency-injection": "For using as a service from the container" + "symfony/config": "^5.4|^6.0", + "symfony/web-profiler-bundle": "^5.4|^6.0" }, "type": "symfony-bundle", "autoload": { @@ -15947,7 +15539,7 @@ "description": "Provides a tight integration of the Symfony VarDumper component and the ServerLogCommand from MonologBridge into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/debug-bundle/tree/v5.4.21" + "source": "https://github.com/symfony/debug-bundle/tree/v6.3.0" }, "funding": [ { @@ -15963,38 +15555,30 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2023-05-25T12:58:06+00:00" }, { "name": "symfony/dom-crawler", - "version": "v5.4.23", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "4a286c916b74ecfb6e2caf1aa31d3fe2a34b7e08" + "reference": "8aa333f41f05afc7fc285a976b58272fd90fc212" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/4a286c916b74ecfb6e2caf1aa31d3fe2a34b7e08", - "reference": "4a286c916b74ecfb6e2caf1aa31d3fe2a34b7e08", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/8aa333f41f05afc7fc285a976b58272fd90fc212", + "reference": "8aa333f41f05afc7fc285a976b58272fd90fc212", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", + "masterminds/html5": "^2.6", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "^1.16" - }, - "conflict": { - "masterminds/html5": "<2.6" + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "masterminds/html5": "^2.6", - "symfony/css-selector": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/css-selector": "" + "symfony/css-selector": "^5.4|^6.0" }, "type": "library", "autoload": { @@ -16022,7 +15606,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v5.4.23" + "source": "https://github.com/symfony/dom-crawler/tree/v6.3.1" }, "funding": [ { @@ -16038,26 +15622,26 @@ "type": "tidelift" } ], - "time": "2023-04-08T21:20:19+00:00" + "time": "2023-06-05T15:30:22+00:00" }, { "name": "symfony/maker-bundle", - "version": "v1.43.0", + "version": "v1.49.0", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "e3f9a1d9e0f4968f68454403e820dffc7db38a59" + "reference": "ce1d424f76bbb377f1956cc7641e8e2eafe81cde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/e3f9a1d9e0f4968f68454403e820dffc7db38a59", - "reference": "e3f9a1d9e0f4968f68454403e820dffc7db38a59", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/ce1d424f76bbb377f1956cc7641e8e2eafe81cde", + "reference": "ce1d424f76bbb377f1956cc7641e8e2eafe81cde", "shasum": "" }, "require": { "doctrine/inflector": "^2.0", "nikic/php-parser": "^4.11", - "php": ">=7.2.5", + "php": ">=8.0", "symfony/config": "^5.4.7|^6.0", "symfony/console": "^5.4.7|^6.0", "symfony/dependency-injection": "^5.4.7|^6.0", @@ -16065,19 +15649,21 @@ "symfony/filesystem": "^5.4.7|^6.0", "symfony/finder": "^5.4.3|^6.0", "symfony/framework-bundle": "^5.4.7|^6.0", - "symfony/http-kernel": "^5.4.7|^6.0" + "symfony/http-kernel": "^5.4.7|^6.0", + "symfony/process": "^5.4.7|^6.0" }, "conflict": { - "doctrine/orm": "<2.10" + "doctrine/doctrine-bundle": "<2.4", + "doctrine/orm": "<2.10", + "symfony/doctrine-bridge": "<5.4" }, "require-dev": { "composer/semver": "^3.0", "doctrine/doctrine-bundle": "^2.4", "doctrine/orm": "^2.10.0", "symfony/http-client": "^5.4.7|^6.0", - "symfony/phpunit-bridge": "^5.4.7|^6.0", + "symfony/phpunit-bridge": "^5.4.17|^6.0", "symfony/polyfill-php80": "^1.16.0", - "symfony/process": "^5.4.7|^6.0", "symfony/security-core": "^5.4.7|^6.0", "symfony/yaml": "^5.4.3|^6.0", "twig/twig": "^2.0|^3.0" @@ -16113,7 +15699,7 @@ ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.43.0" + "source": "https://github.com/symfony/maker-bundle/tree/v1.49.0" }, "funding": [ { @@ -16129,34 +15715,32 @@ "type": "tidelift" } ], - "time": "2022-05-17T15:46:50+00:00" + "time": "2023-06-07T13:10:14+00:00" }, { "name": "symfony/phpunit-bridge", - "version": "v5.4.23", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "1572c5b7cad812bdf0414d89a32a33a2dafb38ba" + "reference": "0b0bf59b0d9bd1422145a123a67fb12af546ef0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/1572c5b7cad812bdf0414d89a32a33a2dafb38ba", - "reference": "1572c5b7cad812bdf0414d89a32a33a2dafb38ba", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/0b0bf59b0d9bd1422145a123a67fb12af546ef0d", + "reference": "0b0bf59b0d9bd1422145a123a67fb12af546ef0d", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/deprecation-contracts": "^2.1|^3" + "php": ">=7.1.3" }, "conflict": { "phpunit/phpunit": "<7.5|9.1.2" }, "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0" - }, - "suggest": { - "symfony/error-handler": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/polyfill-php81": "^1.27" }, "bin": [ "bin/simple-phpunit" @@ -16196,7 +15780,7 @@ "description": "Provides utilities for PHPUnit, especially user deprecation notices management", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/phpunit-bridge/tree/v5.4.23" + "source": "https://github.com/symfony/phpunit-bridge/tree/v6.3.1" }, "funding": [ { @@ -16212,43 +15796,41 @@ "type": "tidelift" } ], - "time": "2023-04-18T09:42:03+00:00" + "time": "2023-06-23T13:25:16+00:00" }, { "name": "symfony/web-profiler-bundle", - "version": "v5.4.24", + "version": "v6.3.1", "source": { "type": "git", "url": "https://github.com/symfony/web-profiler-bundle.git", - "reference": "42dbb751c0363d75a3697775e662d6f21f3d8b83" + "reference": "4a6cf8cb093e720c7ae4d55b1af848ce7e9abd36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/42dbb751c0363d75a3697775e662d6f21f3d8b83", - "reference": "42dbb751c0363d75a3697775e662d6f21f3d8b83", + "url": "https://api.github.com/repos/symfony/web-profiler-bundle/zipball/4a6cf8cb093e720c7ae4d55b1af848ce7e9abd36", + "reference": "4a6cf8cb093e720c7ae4d55b1af848ce7e9abd36", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/framework-bundle": "^5.3|^6.0", - "symfony/http-kernel": "^5.3|^6.0", - "symfony/polyfill-php80": "^1.16", - "symfony/routing": "^4.4|^5.0|^6.0", - "symfony/twig-bundle": "^4.4|^5.0|^6.0", + "php": ">=8.1", + "symfony/config": "^5.4|^6.0", + "symfony/framework-bundle": "^5.4|^6.0", + "symfony/http-kernel": "^6.3", + "symfony/routing": "^5.4|^6.0", + "symfony/twig-bundle": "^5.4|^6.0", "twig/twig": "^2.13|^3.0.4" }, "conflict": { - "symfony/dependency-injection": "<5.2", - "symfony/form": "<4.4", + "symfony/form": "<5.4", "symfony/mailer": "<5.4", - "symfony/messenger": "<4.4" + "symfony/messenger": "<5.4" }, "require-dev": { - "symfony/browser-kit": "^4.4|^5.0|^6.0", - "symfony/console": "^4.4|^5.0|^6.0", - "symfony/css-selector": "^4.4|^5.0|^6.0", - "symfony/stopwatch": "^4.4|^5.0|^6.0" + "symfony/browser-kit": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0" }, "type": "symfony-bundle", "autoload": { @@ -16275,8 +15857,11 @@ ], "description": "Provides a development tool that gives detailed information about the execution of any request", "homepage": "https://symfony.com", + "keywords": [ + "dev" + ], "support": { - "source": "https://github.com/symfony/web-profiler-bundle/tree/v5.4.24" + "source": "https://github.com/symfony/web-profiler-bundle/tree/v6.3.1" }, "funding": [ { @@ -16292,20 +15877,20 @@ "type": "tidelift" } ], - "time": "2023-05-02T16:38:36+00:00" + "time": "2023-06-24T11:51:27+00:00" }, { "name": "symplify/easy-coding-standard", - "version": "11.3.4", + "version": "11.5.0", "source": { "type": "git", "url": "https://github.com/easy-coding-standard/easy-coding-standard.git", - "reference": "c72eb4bdf30e54de2d31aef596f96642ff443741" + "reference": "1d2400f7bfe92e3754ce71f0782f2c0521bade3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/c72eb4bdf30e54de2d31aef596f96642ff443741", - "reference": "c72eb4bdf30e54de2d31aef596f96642ff443741", + "url": "https://api.github.com/repos/easy-coding-standard/easy-coding-standard/zipball/1d2400f7bfe92e3754ce71f0782f2c0521bade3d", + "reference": "1d2400f7bfe92e3754ce71f0782f2c0521bade3d", "shasum": "" }, "require": { @@ -16338,7 +15923,7 @@ ], "support": { "issues": "https://github.com/easy-coding-standard/easy-coding-standard/issues", - "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/11.3.4" + "source": "https://github.com/easy-coding-standard/easy-coding-standard/tree/11.5.0" }, "funding": [ { @@ -16350,20 +15935,20 @@ "type": "github" } ], - "time": "2023-05-10T14:44:08+00:00" + "time": "2023-06-21T06:26:15+00:00" }, { "name": "vimeo/psalm", - "version": "5.12.0", + "version": "5.13.0", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "f90118cdeacd0088e7215e64c0c99ceca819e176" + "reference": "a0a9c27630bcf8301ee78cb06741d2907d8c9fef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/f90118cdeacd0088e7215e64c0c99ceca819e176", - "reference": "f90118cdeacd0088e7215e64c0c99ceca819e176", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/a0a9c27630bcf8301ee78cb06741d2907d8c9fef", + "reference": "a0a9c27630bcf8301ee78cb06741d2907d8c9fef", "shasum": "" }, "require": { @@ -16454,9 +16039,9 @@ ], "support": { "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/5.12.0" + "source": "https://github.com/vimeo/psalm/tree/5.13.0" }, - "time": "2023-05-22T21:19:03+00:00" + "time": "2023-06-24T17:05:12+00:00" } ], "aliases": [], @@ -16468,7 +16053,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "^7.4 || ^8.0", + "php": "^8.1", "ext-ctype": "*", "ext-dom": "*", "ext-gd": "*", @@ -16479,7 +16064,7 @@ }, "platform-dev": [], "platform-overrides": { - "php": "7.4.0" + "php": "8.1.0" }, "plugin-api-version": "2.3.0" } diff --git a/config/bootstrap.php b/config/bootstrap.php deleted file mode 100644 index 55560fb8..00000000 --- a/config/bootstrap.php +++ /dev/null @@ -1,23 +0,0 @@ -=1.2) -if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && (!isset($env['APP_ENV']) || ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV'])) { - (new Dotenv(false))->populate($env); -} else { - // load all the .env files - (new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env'); -} - -$_SERVER += $_ENV; -$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; -$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; -$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; diff --git a/config/bundles.php b/config/bundles.php index 91ddea04..91a5e88a 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -2,7 +2,6 @@ return [ Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], - Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], @@ -27,5 +26,6 @@ return [ Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true], SpomkyLabs\CborBundle\SpomkyLabsCborBundle::class => ['all' => true], Webauthn\Bundle\WebauthnBundle::class => ['all' => true], - Hslavich\OneloginSamlBundle\HslavichOneloginSamlBundle::class => ['all' => true], + Nbgrp\OneloginSamlBundle\NbgrpOneloginSamlBundle::class => ['all' => true], + Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true], ]; diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 5cdb115a..5adb0922 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -21,12 +21,15 @@ doctrine: orm: auto_generate_proxy_classes: true + enable_lazy_ghost_objects: true + report_fields_where_declared: true + validate_xml_mapping: true naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware auto_mapping: true mappings: App: is_bundle: false - type: annotation + type: attribute dir: '%kernel.project_dir%/src/Entity' prefix: 'App\Entity' alias: App diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 6adac2bb..05dc5d0e 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -2,8 +2,9 @@ framework: secret: '%env(APP_SECRET)%' csrf_protection: true + handle_all_throwables: true - # Must be set to true, to enable the change of HTTP methhod via _method parameter, otherwise our delete routines does not work anymore + # Must be set to true, to enable the change of HTTP method via _method parameter, otherwise our delete routines does not work anymore # TODO: Rework delete routines to work without _method parameter as it is not recommended anymore (see https://github.com/symfony/symfony/issues/45278) http_method_override: true @@ -29,9 +30,6 @@ framework: php_errors: log: true - form: - legacy_error_messages: false # Enable to use the new Form component validation messages - when@test: framework: test: true diff --git a/config/packages/hslavich_onelogin_saml.yaml b/config/packages/hslavich_onelogin_saml.yaml deleted file mode 100644 index cae3c539..00000000 --- a/config/packages/hslavich_onelogin_saml.yaml +++ /dev/null @@ -1,60 +0,0 @@ -# See https://github.com/SAML-Toolkits/php-saml for more information about the SAML settings - -hslavich_onelogin_saml: - # Basic settings - idp: - entityId: '%env(string:SAML_IDP_ENTITY_ID)%' - singleSignOnService: - url: '%env(string:SAML_IDP_SINGLE_SIGN_ON_SERVICE)%' - binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' - singleLogoutService: - url: '%env(string:SAML_IDP_SINGLE_LOGOUT_SERVICE)%' - binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' - x509cert: '%env(string:SAML_IDP_X509_CERT)%' - sp: - entityId: '%env(string:SAML_SP_ENTITY_ID)%' - assertionConsumerService: - url: '%partdb.default_uri%saml/acs' - binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' - singleLogoutService: - url: '%partdb.default_uri%logout' - binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' - x509cert: '%env(string:SAML_SP_X509_CERT)%' - privateKey: '%env(string:SAMLP_SP_PRIVATE_KEY)%' - - # Optional settings - #baseurl: 'http://myapp.com' - strict: true - debug: false - security: - allowRepeatAttributeName: true - # nameIdEncrypted: false - authnRequestsSigned: true - logoutRequestSigned: true - logoutResponseSigned: true - # wantMessagesSigned: false - # wantAssertionsSigned: true - # wantNameIdEncrypted: false - # requestedAuthnContext: true - # signMetadata: false - # wantXMLValidation: true - # relaxDestinationValidation: false - # destinationStrictlyMatches: true - # rejectUnsolicitedResponsesWithInResponseTo: false - # signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' - # digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' - #contactPerson: - # technical: - # givenName: 'Tech User' - # emailAddress: 'techuser@example.com' - # support: - # givenName: 'Support User' - # emailAddress: 'supportuser@example.com' - # administrative: - # givenName: 'Administrative User' - # emailAddress: 'administrativeuser@example.com' - #organization: - # en: - # name: 'Part-DB-name' - # displayname: 'Displayname' - # url: 'http://example.com' \ No newline at end of file diff --git a/config/packages/http_discovery.yaml b/config/packages/http_discovery.yaml new file mode 100644 index 00000000..2a789e73 --- /dev/null +++ b/config/packages/http_discovery.yaml @@ -0,0 +1,10 @@ +services: + Psr\Http\Message\RequestFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\ResponseFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\ServerRequestFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\StreamFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\UploadedFileFactoryInterface: '@http_discovery.psr17_factory' + Psr\Http\Message\UriFactoryInterface: '@http_discovery.psr17_factory' + + http_discovery.psr17_factory: + class: Http\Discovery\Psr17Factory diff --git a/config/packages/lock.yaml b/config/packages/lock.yaml deleted file mode 100644 index 574879f8..00000000 --- a/config/packages/lock.yaml +++ /dev/null @@ -1,2 +0,0 @@ -framework: - lock: '%env(LOCK_DSN)%' diff --git a/config/packages/nbgrp_onelogin_saml.yaml b/config/packages/nbgrp_onelogin_saml.yaml new file mode 100644 index 00000000..d2f5bae0 --- /dev/null +++ b/config/packages/nbgrp_onelogin_saml.yaml @@ -0,0 +1,62 @@ +# See https://github.com/SAML-Toolkits/php-saml for more information about the SAML settings + +nbgrp_onelogin_saml: + onelogin_settings: + default: + # Basic settings + idp: + entityId: '%env(string:SAML_IDP_ENTITY_ID)%' + singleSignOnService: + url: '%env(string:SAML_IDP_SINGLE_SIGN_ON_SERVICE)%' + binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' + singleLogoutService: + url: '%env(string:SAML_IDP_SINGLE_LOGOUT_SERVICE)%' + binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' + x509cert: '%env(string:SAML_IDP_X509_CERT)%' + sp: + entityId: '%env(string:SAML_SP_ENTITY_ID)%' + assertionConsumerService: + url: '%partdb.default_uri%saml/acs' + binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' + singleLogoutService: + url: '%partdb.default_uri%logout' + binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' + x509cert: '%env(string:SAML_SP_X509_CERT)%' + privateKey: '%env(string:SAMLP_SP_PRIVATE_KEY)%' + + # Optional settings + #baseurl: 'http://myapp.com' + strict: true + debug: false + security: + allowRepeatAttributeName: true + # nameIdEncrypted: false + authnRequestsSigned: true + logoutRequestSigned: true + logoutResponseSigned: true + # wantMessagesSigned: false + # wantAssertionsSigned: true + # wantNameIdEncrypted: false + # requestedAuthnContext: true + # signMetadata: false + # wantXMLValidation: true + # relaxDestinationValidation: false + # destinationStrictlyMatches: true + # rejectUnsolicitedResponsesWithInResponseTo: false + # signatureAlgorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' + # digestAlgorithm: 'http://www.w3.org/2001/04/xmlenc#sha256' + #contactPerson: + # technical: + # givenName: 'Tech User' + # emailAddress: 'techuser@example.com' + # support: + # givenName: 'Support User' + # emailAddress: 'supportuser@example.com' + # administrative: + # givenName: 'Administrative User' + # emailAddress: 'administrativeuser@example.com' + #organization: + # en: + # name: 'Part-DB-name' + # displayname: 'Displayname' + # url: 'http://example.com' \ No newline at end of file diff --git a/config/packages/scheb_2fa.yaml b/config/packages/scheb_2fa.yaml index f58bdacc..62a6611d 100644 --- a/config/packages/scheb_2fa.yaml +++ b/config/packages/scheb_2fa.yaml @@ -1,4 +1,4 @@ -# See the configuration reference at https://symfony.com/bundles/SchebTwoFactorBundle/5.x/configuration.html +# See the configuration reference at https://symfony.com/bundles/SchebTwoFactorBundle/6.x/configuration.html scheb_two_factor: google: @@ -23,6 +23,6 @@ scheb_two_factor: security_tokens: - Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken # If you're using guard-based authentication, you have to use this one: - # - Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken + # - Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken # If you're using authenticator-based security (introduced in Symfony 5.1), you have to use this one: - # - Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken \ No newline at end of file + - Symfony\Component\Security\Http\Authenticator\Token\PostAuthenticationToken diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 7e0ded3c..342e38bf 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -1,6 +1,5 @@ security: - enable_authenticator_manager: true - + # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords password_hashers: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' diff --git a/config/packages/sensio_framework_extra.yaml b/config/packages/sensio_framework_extra.yaml deleted file mode 100644 index 1821ccc0..00000000 --- a/config/packages/sensio_framework_extra.yaml +++ /dev/null @@ -1,3 +0,0 @@ -sensio_framework_extra: - router: - annotations: false diff --git a/config/packages/test/framework.yaml b/config/packages/test/framework.yaml index d051c840..f76cc2ef 100644 --- a/config/packages/test/framework.yaml +++ b/config/packages/test/framework.yaml @@ -1,4 +1,2 @@ framework: test: true - session: - storage_id: session.storage.mock_file diff --git a/config/packages/uid.yaml b/config/packages/uid.yaml new file mode 100644 index 00000000..01520944 --- /dev/null +++ b/config/packages/uid.yaml @@ -0,0 +1,4 @@ +framework: + uid: + default_uuid_version: 7 + time_based_uuid_version: 7 diff --git a/config/packages/web_profiler.yaml b/config/packages/web_profiler.yaml index 17893da1..b9461110 100644 --- a/config/packages/web_profiler.yaml +++ b/config/packages/web_profiler.yaml @@ -4,7 +4,9 @@ when@dev: intercept_redirects: false framework: - profiler: { only_exceptions: false } + profiler: + only_exceptions: false + collect_serializer_data: true when@test: web_profiler: diff --git a/config/parameters.yaml b/config/parameters.yaml index ba63a202..1cfa41a4 100644 --- a/config/parameters.yaml +++ b/config/parameters.yaml @@ -19,7 +19,7 @@ parameters: ###################################################################################################################### # Users and Privacy ###################################################################################################################### - partdb.gpdr_compliance: true # If this option is activated, IP addresses are anonymized to be GPDR compliant + partdb.gdpr_compliance: true # If this option is activated, IP addresses are anonymized to be GDPR compliant partdb.users.use_gravatar: '%env(bool:USE_GRAVATAR)%' # Set to false, if no Gravatar images should be used for user profiles. partdb.users.email_pw_reset: '%env(bool:ALLOW_EMAIL_PW_RESET)%' # Config if users are able, to reset their password by email. By default this enabled, when a mail server is configured. diff --git a/config/routes.yaml b/config/routes.yaml index 7d495d6d..777ade11 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -1,7 +1,3 @@ -#index: -# path: / -# controller: App\Controller\DefaultController::index - # Redirect every url without an locale to the locale of the user/the global base locale scan_qr: diff --git a/config/routes/annotations.yaml b/config/routes/attributes.yaml similarity index 66% rename from config/routes/annotations.yaml rename to config/routes/attributes.yaml index bd9ea273..72d7c9bc 100644 --- a/config/routes/annotations.yaml +++ b/config/routes/attributes.yaml @@ -1,6 +1,8 @@ controllers: - resource: ../../src/Controller/ - type: annotation + resource: + path: ../../src/Controller/ + namespace: App\Controller + type: attribute prefix: '{_locale}' defaults: @@ -11,4 +13,4 @@ controllers: kernel: resource: ../../src/Kernel.php - type: annotation + type: attribute diff --git a/config/routes/hslavich_saml.yaml b/config/routes/nbgrp_saml.yaml similarity index 58% rename from config/routes/hslavich_saml.yaml rename to config/routes/nbgrp_saml.yaml index a902a93e..6bf45795 100644 --- a/config/routes/hslavich_saml.yaml +++ b/config/routes/nbgrp_saml.yaml @@ -1,4 +1,4 @@ -hslavich_saml_sp: - resource: "@HslavichOneloginSamlBundle/Resources/config/routing.yml" +nbgrp_saml: + resource: "@NbgrpOneloginSamlBundle/Resources/config/routes.php" # Only load the SAML routes if SAML is enabled condition: "env('SAML_ENABLED') == '1' or env('SAML_ENABLED') == 'true'" diff --git a/config/services.yaml b/config/services.yaml index 84b80fdd..9e15eca0 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -14,11 +14,11 @@ services: autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. bind: bool $demo_mode: '%partdb.demo_mode%' - bool $gpdr_compliance : '%partdb.gpdr_compliance%' - bool $kernel_debug: '%kernel.debug%' + bool $gdpr_compliance: '%partdb.gdpr_compliance%' + bool $kernel_debug_enabled: '%kernel.debug%' string $kernel_cache_dir: '%kernel.cache_dir%' string $partdb_title: '%partdb.title%' - string $default_currency: '%partdb.default_currency%' + string $base_currency: '%partdb.default_currency%' _instanceof: App\Services\LabelSystem\PlaceholderProviders\PlaceholderProviderInterface: @@ -88,7 +88,7 @@ services: App\Form\AttachmentFormType: arguments: - $allow_attachments_downloads: '%partdb.attachments.allow_downloads%' + $allow_attachments_download: '%partdb.attachments.allow_downloads%' $max_file_size: '%partdb.attachments.max_file_size%' App\Services\Attachments\AttachmentSubmitHandler: @@ -97,12 +97,6 @@ services: $mimeTypes: '@mime_types' $max_upload_size: '%partdb.attachments.max_file_size%' - App\EventSubscriber\LogSystem\LogoutLoggerListener: - tags: - - name: 'kernel.event_listener' - event: 'Symfony\Component\Security\Http\Event\LogoutEvent' - dispatcher: security.event_dispatcher.main - App\Services\LogSystem\EventCommentNeededHelper: arguments: $enforce_change_comments_for: '%partdb.enforce_change_comments_for%' @@ -183,7 +177,7 @@ services: App\EventSubscriber\UserSystem\SetUserTimezoneSubscriber: arguments: - $timezone: '%partdb.timezone%' + $default_timezone: '%partdb.timezone%' App\Controller\SecurityController: arguments: @@ -265,7 +259,7 @@ services: tags: - { name: 'doctrine.fixtures.purger_factory', alias: 'reset_autoincrement_purger' } - # We are needing this service inside of a migration, where only the container is injected. So we need to define it as public, to access it from the container. + # We are needing this service inside a migration, where only the container is injected. So we need to define it as public, to access it from the container. App\Services\UserSystem\PermissionPresetsHelper: public: true @@ -289,3 +283,14 @@ services: autowire: true tags: - { name: monolog.processor } + +when@test: + services: + # Decorate the doctrine fixtures load command to use our custom purger by default + doctrine.fixtures_load_command.custom: + decorates: doctrine.fixtures_load_command + class: Doctrine\Bundle\FixturesBundle\Command\LoadDataFixturesDoctrineCommand + arguments: + - '@doctrine.fixtures.loader' + - '@doctrine' + - { default: '@App\Doctrine\Purger\ResetAutoIncrementPurgerFactory' } \ No newline at end of file diff --git a/docs/configuration.md b/docs/configuration.md index 39a714e7..f7748195 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -98,7 +98,7 @@ The following options are available: * `partdb.global_theme`: The default theme to use, when no user specific theme is set. Should be one of the themes from the `partdb.available_themes` config option. * `partdb.locale_menu`: The codes of the languages, which should be shown in the language chooser menu (the one with the user icon in the navbar). The first language in the list will be the default language. -* `partdb.gpdr_compliance`: When set to true (default value), IP addresses which are saved in the database will be anonymized, by removing the last byte of the IP. This is required by the GDPR (General Data Protection Regulation) in the EU. +* `partdb.gdpr_compliance`: When set to true (default value), IP addresses which are saved in the database will be anonymized, by removing the last byte of the IP. This is required by the GDPR (General Data Protection Regulation) in the EU. * `partdb.sidebar.items`: The panel contents which should be shown in the sidebar by default. You can also change the number of sidebar panels by changing the number of items in this list. * `partdb.sidebar.root_node_enable`: Show a root node in the sidebar trees, of which all nodes are children of * `partdb.sidebar.root_expanded`: Expand the root node in the sidebar trees by default diff --git a/migrations/Version20230417211732.php b/migrations/Version20230417211732.php index c60d7c0b..382f6650 100644 --- a/migrations/Version20230417211732.php +++ b/migrations/Version20230417211732.php @@ -8,9 +8,6 @@ use App\Migration\AbstractMultiPlatformMigration; use Doctrine\DBAL\Schema\Schema; use Doctrine\Migrations\AbstractMigration; -/** - * Auto-generated Migration: Please modify to your needs! - */ final class Version20230417211732 extends AbstractMultiPlatformMigration { public function getDescription(): string diff --git a/migrations/Version20230528000149.php b/migrations/Version20230528000149.php new file mode 100644 index 00000000..05830937 --- /dev/null +++ b/migrations/Version20230528000149.php @@ -0,0 +1,65 @@ +addSql('ALTER TABLE webauthn_keys ADD other_ui LONGTEXT DEFAULT NULL COMMENT \'(DC2Type:array)\''); + } + + public function mySQLDown(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE webauthn_keys DROP other_ui'); + } + + public function sqLiteUp(Schema $schema): void + { + $this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys'); + $this->addSql('DROP TABLE webauthn_keys'); + $this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL --(DC2Type:base64) + , type VARCHAR(255) NOT NULL, transports CLOB NOT NULL --(DC2Type:array) + , attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL --(DC2Type:trust_path) + , aaguid CLOB NOT NULL --(DC2Type:aaguid) + , credential_public_key CLOB NOT NULL --(DC2Type:base64) + , user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, other_ui CLOB DEFAULT NULL --(DC2Type:array) + , CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON UPDATE NO ACTION ON DELETE NO ACTION NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys'); + $this->addSql('DROP TABLE __temp__webauthn_keys'); + $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); + } + + public function sqLiteDown(Schema $schema): void + { + $this->addSql('CREATE TEMPORARY TABLE __temp__webauthn_keys AS SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM webauthn_keys'); + $this->addSql('DROP TABLE webauthn_keys'); + $this->addSql('CREATE TABLE webauthn_keys (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, public_key_credential_id CLOB NOT NULL -- +(DC2Type:base64) + , type VARCHAR(255) NOT NULL, transports CLOB NOT NULL -- +(DC2Type:array) + , attestation_type VARCHAR(255) NOT NULL, trust_path CLOB NOT NULL -- +(DC2Type:trust_path) + , aaguid CLOB NOT NULL -- +(DC2Type:aaguid) + , credential_public_key CLOB NOT NULL -- +(DC2Type:base64) + , user_handle VARCHAR(255) NOT NULL, counter INTEGER NOT NULL, name VARCHAR(255) NOT NULL, last_modified DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, datetime_added DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, CONSTRAINT FK_799FD143A76ED395 FOREIGN KEY (user_id) REFERENCES "users" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO webauthn_keys (id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added) SELECT id, user_id, public_key_credential_id, type, transports, attestation_type, trust_path, aaguid, credential_public_key, user_handle, counter, name, last_modified, datetime_added FROM __temp__webauthn_keys'); + $this->addSql('DROP TABLE __temp__webauthn_keys'); + $this->addSql('CREATE INDEX IDX_799FD143A76ED395 ON webauthn_keys (user_id)'); + } +} diff --git a/package.json b/package.json index 8c5d7599..3fb2eada 100644 --- a/package.json +++ b/package.json @@ -28,45 +28,44 @@ "build": "encore production --progress" }, "dependencies": { - "@ckeditor/ckeditor5-alignment": "^37.1.0", - "@ckeditor/ckeditor5-autoformat": "^37.1.0", - "@ckeditor/ckeditor5-basic-styles": "^37.1.0", - "@ckeditor/ckeditor5-block-quote": "^37.1.0", - "@ckeditor/ckeditor5-code-block": "^37.1.0", - "@ckeditor/ckeditor5-dev-utils": "^37.0.0", + "@ckeditor/ckeditor5-alignment": "^38.0.1", + "@ckeditor/ckeditor5-autoformat": "^38.0.1", + "@ckeditor/ckeditor5-basic-styles": "^38.0.1", + "@ckeditor/ckeditor5-block-quote": "^38.0.1", + "@ckeditor/ckeditor5-code-block": "^38.0.1", + "@ckeditor/ckeditor5-dev-utils": "^38.0.1", "@ckeditor/ckeditor5-dev-webpack-plugin": "^31.1.13", - "@ckeditor/ckeditor5-editor-classic": "^37.1.0", - "@ckeditor/ckeditor5-essentials": "^37.1.0", - "@ckeditor/ckeditor5-find-and-replace": "^37.1.0", - "@ckeditor/ckeditor5-font": "^37.1.0", - "@ckeditor/ckeditor5-heading": "^37.1.0", - "@ckeditor/ckeditor5-highlight": "^37.1.0", - "@ckeditor/ckeditor5-horizontal-line": "^37.1.0", - "@ckeditor/ckeditor5-html-embed": "^37.1.0", - "@ckeditor/ckeditor5-html-support": "^37.1.0", - "@ckeditor/ckeditor5-image": "^37.1.0", - "@ckeditor/ckeditor5-indent": "^37.1.0", - "@ckeditor/ckeditor5-link": "^37.1.0", - "@ckeditor/ckeditor5-list": "^37.1.0", - "@ckeditor/ckeditor5-markdown-gfm": "^37.1.0", - "@ckeditor/ckeditor5-media-embed": "^37.1.0", - "@ckeditor/ckeditor5-paragraph": "^37.1.0", - "@ckeditor/ckeditor5-paste-from-office": "^37.1.0", - "@ckeditor/ckeditor5-remove-format": "^37.1.0", - "@ckeditor/ckeditor5-source-editing": "^37.1.0", - "@ckeditor/ckeditor5-special-characters": "^37.1.0", - "@ckeditor/ckeditor5-table": "^37.1.0", - "@ckeditor/ckeditor5-theme-lark": "^37.1.0", - "@ckeditor/ckeditor5-upload": "^37.1.0", - "@ckeditor/ckeditor5-watchdog": "^37.1.0", - "@ckeditor/ckeditor5-word-count": "^37.1.0", + "@ckeditor/ckeditor5-editor-classic": "^38.0.1", + "@ckeditor/ckeditor5-essentials": "^38.0.1", + "@ckeditor/ckeditor5-find-and-replace": "^38.0.1", + "@ckeditor/ckeditor5-font": "^38.0.1", + "@ckeditor/ckeditor5-heading": "^38.0.1", + "@ckeditor/ckeditor5-highlight": "^38.0.1", + "@ckeditor/ckeditor5-horizontal-line": "^38.0.1", + "@ckeditor/ckeditor5-html-embed": "^38.0.1", + "@ckeditor/ckeditor5-html-support": "^38.0.1", + "@ckeditor/ckeditor5-image": "^38.0.1", + "@ckeditor/ckeditor5-indent": "^38.0.1", + "@ckeditor/ckeditor5-link": "^38.0.1", + "@ckeditor/ckeditor5-list": "^38.0.1", + "@ckeditor/ckeditor5-markdown-gfm": "^38.0.1", + "@ckeditor/ckeditor5-media-embed": "^38.0.1", + "@ckeditor/ckeditor5-paragraph": "^38.0.1", + "@ckeditor/ckeditor5-paste-from-office": "^38.0.1", + "@ckeditor/ckeditor5-remove-format": "^38.0.1", + "@ckeditor/ckeditor5-source-editing": "^38.0.1", + "@ckeditor/ckeditor5-special-characters": "^38.0.1", + "@ckeditor/ckeditor5-table": "^38.0.1", + "@ckeditor/ckeditor5-theme-lark": "^38.0.1", + "@ckeditor/ckeditor5-upload": "^38.0.1", + "@ckeditor/ckeditor5-watchdog": "^38.0.1", + "@ckeditor/ckeditor5-word-count": "^38.0.1", "@jbtronics/bs-treeview": "^1.0.1", "bootbox": "^6.0.0", "bootswatch": "^5.1.3", "bs-custom-file-input": "^1.3.4", "clipboard": "^2.0.4", "compression-webpack-plugin": "^10.0.0", - "darkmode-js": "^1.5.0", "datatables.net-bs5": "^1.10.20", "datatables.net-buttons-bs5": "^2.2.2", "datatables.net-colreorder-bs5": "^1.5.1", @@ -80,7 +79,9 @@ "json-formatter-js": "^2.3.4", "jszip": "^3.2.0", "katex": "^0.16.0", - "marked": "^4.3.0", + "marked": "^5.1.0", + "marked-gfm-heading-id": "^3.0.4", + "marked-mangle": "^1.0.1", "pdfmake": "^0.2.2", "stimulus-use": "^0.52.0", "tom-select": "^2.1.0", diff --git a/phpstan.neon b/phpstan.neon index e0e0e662..db118378 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,11 +1,55 @@ parameters: + + level: 5 + + paths: + - src + # - tests + + excludePaths: + - src/DataTables/Adapter/* + - src/Configuration/* + - src/Doctrine/Purger/* + + + inferPrivatePropertyTypeFromConstructor: true treatPhpDocTypesAsCertain: false symfony: container_xml_path: '%rootDir%/../../../var/cache/dev/App_KernelDevDebugContainer.xml' - excludes_analyse: - - src/DataTables/Adapter/* - - src/Configuration/* - - src/Doctrine/Purger/* \ No newline at end of file + checkUninitializedProperties: true + + checkFunctionNameCase: true + + checkAlwaysTrueInstanceof: false + checkAlwaysTrueCheckTypeFunctionCall: false + checkAlwaysTrueStrictComparison: false + reportAlwaysTrueInLastCondition: false + + reportMaybesInPropertyPhpDocTypes: false + reportMaybesInMethodSignatures: false + + strictRules: + disallowedLooseComparison: false + booleansInConditions: false + uselessCast: false + requireParentConstructorCall: true + disallowedConstructs: false + overwriteVariablesWithLoop: false + closureUsesThis: false + matchingInheritedMethodNames: true + numericOperandsInArithmeticOperators: true + strictCalls: true + switchConditionsMatchingType: false + noVariableVariables: false + + ignoreErrors: + # Ignore errors caused by complex mapping with AbstractStructuralDBElement + - '#AbstractStructuralDBElement does not have a field named \$parent#' + - '#AbstractStructuralDBElement does not have a field named \$name#' + + # Ignore errors related to the use of the ParametersTrait in Part entity + - '#expects .*PartParameter, .*AbstractParameter given.#' + - '#Part::getParameters\(\) should return .*AbstractParameter#' \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 94534e9d..59622803 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,20 +1,27 @@ - - - - src - - + - + + + + src + + + tests diff --git a/rector.php b/rector.php new file mode 100644 index 00000000..94ade3df --- /dev/null +++ b/rector.php @@ -0,0 +1,63 @@ +symfonyContainerXml(__DIR__ . '/var/cache/dev/App_KernelDevDebugContainer.xml'); + $rectorConfig->symfonyContainerPhp(__DIR__ . '/tests/symfony-container.php'); + + //Import class names instead of using fully qualified class names + $rectorConfig->importNames(); + //But keep the fully qualified class names for classes in the global namespace + $rectorConfig->importShortClasses(false); + + $rectorConfig->paths([ + __DIR__ . '/config', + __DIR__ . '/public', + __DIR__ . '/src', + __DIR__ . '/tests', + ]); + + // register a single rule + //$rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); + + $rectorConfig->rules([ + DeclareStrictTypesRector::class, + ]); + + // define sets of rules + $rectorConfig->sets([ + //PHP rules + SetList::CODE_QUALITY, + LevelSetList::UP_TO_PHP_81, + + //Symfony rules + SymfonyLevelSetList::UP_TO_SYMFONY_62, + SymfonySetList::SYMFONY_CODE_QUALITY, + + //Doctrine rules + DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES, + DoctrineSetList::DOCTRINE_CODE_QUALITY, + + //PHPUnit rules + PHPUnitLevelSetList::UP_TO_PHPUNIT_90, + PHPUnitSetList::PHPUNIT_CODE_QUALITY, + ]); + + $rectorConfig->skip([ + AddDoesNotPerformAssertionToNonAssertingTestRector::class, + CountArrayToEmptyArrayComparisonRector::class, + ]); +}; diff --git a/src/Command/Attachments/CleanAttachmentsCommand.php b/src/Command/Attachments/CleanAttachmentsCommand.php index 40f568bf..e9ffd286 100644 --- a/src/Command/Attachments/CleanAttachmentsCommand.php +++ b/src/Command/Attachments/CleanAttachmentsCommand.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Command\Attachments; +use Symfony\Component\Console\Attribute\AsCommand; use App\Services\Attachments\AttachmentManager; use App\Services\Attachments\AttachmentPathResolver; use App\Services\Attachments\AttachmentReverseSearch; @@ -40,30 +41,21 @@ use function count; use const DIRECTORY_SEPARATOR; +#[AsCommand('partdb:attachments:clean-unused|app:clean-attachments', 'Lists (and deletes if wanted) attachments files that are not used anymore (abandoned files).')] class CleanAttachmentsCommand extends Command { - protected static $defaultName = 'partdb:attachments:clean-unused|app:clean-attachments'; - - protected AttachmentManager $attachment_helper; - protected AttachmentReverseSearch $reverseSearch; protected MimeTypes $mimeTypeGuesser; - protected AttachmentPathResolver $pathResolver; - public function __construct(AttachmentManager $attachmentHelper, AttachmentReverseSearch $reverseSearch, AttachmentPathResolver $pathResolver) + public function __construct(protected AttachmentManager $attachment_helper, protected AttachmentReverseSearch $reverseSearch, protected AttachmentPathResolver $pathResolver) { - $this->attachment_helper = $attachmentHelper; - $this->pathResolver = $pathResolver; - $this->reverseSearch = $reverseSearch; $this->mimeTypeGuesser = new MimeTypes(); parent::__construct(); } protected function configure(): void { - $this - ->setDescription('Lists (and deletes if wanted) attachments files that are not used anymore (abandoned files).') - ->setHelp('This command allows to find all files in the media folder which are not associated with an attachment anymore.'. - ' These files are not needed and can eventually deleted.'); + $this->setHelp('This command allows to find all files in the media folder which are not associated with an attachment anymore.'. + ' These files are not needed and can eventually deleted.'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -91,7 +83,7 @@ class CleanAttachmentsCommand extends Command foreach ($finder as $file) { //If not attachment object uses this file, print it - if (0 === count($this->reverseSearch->findAttachmentsByFile($file))) { + if ([] === $this->reverseSearch->findAttachmentsByFile($file)) { $file_list[] = $file; $table->addRow([ $fs->makePathRelative($file->getPathname(), $mediaPath), @@ -101,14 +93,14 @@ class CleanAttachmentsCommand extends Command } } - if (count($file_list) > 0) { + if ($file_list !== []) { $table->render(); $continue = $io->confirm(sprintf('Found %d abandoned files. Do you want to delete them? This can not be undone!', count($file_list)), false); if (!$continue) { //We are finished here, when no files should be deleted - return 0; + return Command::SUCCESS; } //Delete the files @@ -121,7 +113,7 @@ class CleanAttachmentsCommand extends Command $io->success('No abandoned files found.'); } - return 0; + return Command::SUCCESS; } /** diff --git a/src/Command/BackupCommand.php b/src/Command/BackupCommand.php index d12add92..ef7d038f 100644 --- a/src/Command/BackupCommand.php +++ b/src/Command/BackupCommand.php @@ -1,7 +1,11 @@ project_dir = $project_dir; - $this->entityManager = $entityManager; - parent::__construct(); } @@ -71,13 +67,10 @@ class BackupCommand extends Command $io->info('Backup Part-DB to '.$output_filepath); //Check if the file already exists - if (file_exists($output_filepath)) { - //Then ask the user, if he wants to overwrite the file - if (!$io->confirm('The file '.realpath($output_filepath).' already exists. Do you want to overwrite it?', false)) { - $io->error('Backup aborted!'); - - return Command::FAILURE; - } + //Then ask the user, if he wants to overwrite the file + if (file_exists($output_filepath) && !$io->confirm('The file '.realpath($output_filepath).' already exists. Do you want to overwrite it?', false)) { + $io->error('Backup aborted!'); + return Command::FAILURE; } $io->note('Starting backup...'); @@ -115,8 +108,6 @@ class BackupCommand extends Command /** * Constructs the MySQL PDO DSN. * Taken from https://github.com/doctrine/dbal/blob/3.5.x/src/Driver/PDO/MySQL/Driver.php - * - * @param array $params */ private function configureDumper(array $params, DbDumper $dumper): void { @@ -166,7 +157,7 @@ class BackupCommand extends Command $io->error('Could not dump database: '.$e->getMessage()); $io->error('This can maybe be fixed by installing the mysqldump binary and adding it to the PATH variable!'); } - } elseif ($connection->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\SqlitePlatform) { + } elseif ($connection->getDatabasePlatform() instanceof SqlitePlatform) { $io->note('SQLite database detected. Copy DB file to ZIP...'); $params = $connection->getParams(); $zip->addFile($params['path'], 'var/app.db'); diff --git a/src/Command/CheckRequirementsCommand.php b/src/Command/CheckRequirementsCommand.php index 4d9f9d96..068147e2 100644 --- a/src/Command/CheckRequirementsCommand.php +++ b/src/Command/CheckRequirementsCommand.php @@ -1,4 +1,7 @@ . */ - namespace App\Command; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -27,23 +30,17 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; +#[AsCommand('partdb:check-requirements', 'Checks if the requirements Part-DB needs or recommends are fulfilled.')] class CheckRequirementsCommand extends Command { - protected static $defaultName = 'partdb:check-requirements'; - - protected ContainerBagInterface $params; - - public function __construct(ContainerBagInterface $params) + public function __construct(protected ContainerBagInterface $params) { - $this->params = $params; parent::__construct(); } protected function configure(): void { - $this - ->setDescription('Checks if the requirements Part-DB needs or recommends are fulfilled.') - ->addOption('only_issues', 'i', InputOption::VALUE_NONE, 'Only show issues, not success messages.') + $this->addOption('only_issues', 'i', InputOption::VALUE_NONE, 'Only show issues, not success messages.') ; } @@ -66,105 +63,124 @@ class CheckRequirementsCommand extends Command } - protected function checkPHP(SymfonyStyle $io, $only_issues = false): void + protected function checkPHP(SymfonyStyle $io, bool $only_issues = false): void { //Check PHP versions - $io->isVerbose() && $io->comment('Checking PHP version...'); - if (PHP_VERSION_ID < 80100) { + if ($io->isVerbose()) { + $io->comment('Checking PHP version...'); + } + //We recommend PHP 8.2, but 8.1 is the minimum + if (PHP_VERSION_ID < 80200) { $io->warning('You are using PHP '. PHP_VERSION .'. This will work, but a newer version is recommended.'); - } else { - !$only_issues && $io->success('PHP version is sufficient.'); + } elseif (!$only_issues) { + $io->success('PHP version is sufficient.'); } //Check if opcache is enabled - $io->isVerbose() && $io->comment('Checking Opcache...'); + if ($io->isVerbose()) { + $io->comment('Checking Opcache...'); + } $opcache_enabled = ini_get('opcache.enable') === '1'; if (!$opcache_enabled) { $io->warning('Opcache is not enabled. This will work, but performance will be better with opcache enabled. Set opcache.enable=1 in your php.ini to enable it'); - } else { - !$only_issues && $io->success('Opcache is enabled.'); + } elseif (!$only_issues) { + $io->success('Opcache is enabled.'); } //Check if opcache is configured correctly - $io->isVerbose() && $io->comment('Checking Opcache configuration...'); + if ($io->isVerbose()) { + $io->comment('Checking Opcache configuration...'); + } if ($opcache_enabled && (ini_get('opcache.memory_consumption') < 256 || ini_get('opcache.max_accelerated_files') < 20000)) { $io->warning('Opcache configuration can be improved. See https://symfony.com/doc/current/performance.html for more info.'); - } else { - !$only_issues && $io->success('Opcache configuration is already performance optimized.'); + } elseif (!$only_issues) { + $io->success('Opcache configuration is already performance optimized.'); } } - protected function checkPartDBConfig(SymfonyStyle $io, $only_issues = false): void + protected function checkPartDBConfig(SymfonyStyle $io, bool $only_issues = false): void { //Check if APP_ENV is set to prod - $io->isVerbose() && $io->comment('Checking debug mode...'); - if($this->params->get('kernel.debug')) { + if ($io->isVerbose()) { + $io->comment('Checking debug mode...'); + } + if ($this->params->get('kernel.debug')) { $io->warning('You have activated debug mode, this is will leak informations in a production environment.'); - } else { - !$only_issues && $io->success('Debug mode disabled.'); + } elseif (!$only_issues) { + $io->success('Debug mode disabled.'); } } - protected function checkPHPExtensions(SymfonyStyle $io, $only_issues = false): void + protected function checkPHPExtensions(SymfonyStyle $io, bool $only_issues = false): void { //Get all installed PHP extensions $extensions = get_loaded_extensions(); - $io->isVerbose() && $io->comment('Your PHP installation has '. count($extensions) .' extensions installed: '. implode(', ', $extensions)); + if ($io->isVerbose()) { + $io->comment('Your PHP installation has '. count($extensions) .' extensions installed: '. implode(', ', $extensions)); + } $db_drivers_count = 0; - if(!in_array('pdo_mysql', $extensions)) { + if(!in_array('pdo_mysql', $extensions, true)) { $io->error('pdo_mysql is not installed. You will not be able to use MySQL databases.'); } else { - !$only_issues && $io->success('PHP extension pdo_mysql is installed.'); + if (!$only_issues) { + $io->success('PHP extension pdo_mysql is installed.'); + } $db_drivers_count++; } - if(!in_array('pdo_sqlite', $extensions)) { + if(!in_array('pdo_sqlite', $extensions, true)) { $io->error('pdo_sqlite is not installed. You will not be able to use SQLite. databases'); } else { - !$only_issues && $io->success('PHP extension pdo_sqlite is installed.'); + if (!$only_issues) { + $io->success('PHP extension pdo_sqlite is installed.'); + } $db_drivers_count++; } - $io->isVerbose() && $io->comment('You have '. $db_drivers_count .' database drivers installed.'); + if ($io->isVerbose()) { + $io->comment('You have '. $db_drivers_count .' database drivers installed.'); + } if ($db_drivers_count === 0) { $io->error('You have no database drivers installed. You have to install at least one database driver!'); } - if(!in_array('curl', $extensions)) { + if (!in_array('curl', $extensions, true)) { $io->warning('curl extension is not installed. Install curl extension for better performance'); - } else { - !$only_issues && $io->success('PHP extension curl is installed.'); + } elseif (!$only_issues) { + $io->success('PHP extension curl is installed.'); } - $gd_installed = in_array('gd', $extensions); - if(!$gd_installed) { + $gd_installed = in_array('gd', $extensions, true); + if (!$gd_installed) { $io->error('GD is not installed. GD is required for image processing.'); - } else { - !$only_issues && $io->success('PHP extension GD is installed.'); + } elseif (!$only_issues) { + $io->success('PHP extension GD is installed.'); } //Check if GD has jpeg support - $io->isVerbose() && $io->comment('Checking if GD has jpeg support...'); + if ($io->isVerbose()) { + $io->comment('Checking if GD has jpeg support...'); + } if ($gd_installed) { $gd_info = gd_info(); - if($gd_info['JPEG Support'] === false) { + if ($gd_info['JPEG Support'] === false) { $io->warning('Your GD does not have jpeg support. You will not be able to generate thumbnails of jpeg images.'); - } else { - !$only_issues && $io->success('GD has jpeg support.'); + } elseif (!$only_issues) { + $io->success('GD has jpeg support.'); } - if($gd_info['PNG Support'] === false) { + if ($gd_info['PNG Support'] === false) { $io->warning('Your GD does not have png support. You will not be able to generate thumbnails of png images.'); - } else { - !$only_issues && $io->success('GD has png support.'); + } elseif (!$only_issues) { + $io->success('GD has png support.'); } - if($gd_info['WebP Support'] === false) { + if ($gd_info['WebP Support'] === false) { $io->warning('Your GD does not have WebP support. You will not be able to generate thumbnails of WebP images.'); - } else { - !$only_issues && $io->success('GD has WebP support.'); + } elseif (!$only_issues) { + $io->success('GD has WebP support.'); } } @@ -173,4 +189,4 @@ class CheckRequirementsCommand extends Command } -} \ No newline at end of file +} diff --git a/src/Command/Currencies/UpdateExchangeRatesCommand.php b/src/Command/Currencies/UpdateExchangeRatesCommand.php index b0735cbc..0f3eb11f 100644 --- a/src/Command/Currencies/UpdateExchangeRatesCommand.php +++ b/src/Command/Currencies/UpdateExchangeRatesCommand.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Command\Currencies; +use Symfony\Component\Console\Attribute\AsCommand; use App\Entity\PriceInformations\Currency; use App\Services\Tools\ExchangeRateUpdater; use Doctrine\ORM\EntityManagerInterface; @@ -35,30 +36,17 @@ use Symfony\Component\Console\Style\SymfonyStyle; use function count; use function strlen; +#[AsCommand('partdb:currencies:update-exchange-rates|partdb:update-exchange-rates|app:update-exchange-rates', 'Updates the currency exchange rates.')] class UpdateExchangeRatesCommand extends Command { - protected static $defaultName = 'partdb:currencies:update-exchange-rates|partdb:update-exchange-rates|app:update-exchange-rates'; - - protected string $base_current; - protected EntityManagerInterface $em; - protected ExchangeRateUpdater $exchangeRateUpdater; - - public function __construct(string $base_current, EntityManagerInterface $entityManager, ExchangeRateUpdater $exchangeRateUpdater) + public function __construct(protected string $base_current, protected EntityManagerInterface $em, protected ExchangeRateUpdater $exchangeRateUpdater) { - //$this->swap = $swap; - $this->base_current = $base_current; - - $this->em = $entityManager; - $this->exchangeRateUpdater = $exchangeRateUpdater; - parent::__construct(); } protected function configure(): void { - $this - ->setDescription('Updates the currency exchange rates.') - ->addArgument('iso_code', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The ISO Codes of the currencies that should be updated.'); + $this->addArgument('iso_code', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'The ISO Codes of the currencies that should be updated.'); } protected function execute(InputInterface $input, OutputInterface $output): int @@ -69,7 +57,7 @@ class UpdateExchangeRatesCommand extends Command if (3 !== strlen($this->base_current)) { $io->error('Chosen Base current is not valid. Check your settings!'); - return 1; + return Command::FAILURE; } $io->note('Update currency exchange rates with base currency: '.$this->base_current); @@ -78,11 +66,7 @@ class UpdateExchangeRatesCommand extends Command $iso_code = $input->getArgument('iso_code'); $repo = $this->em->getRepository(Currency::class); - if (!empty($iso_code)) { - $candidates = $repo->findBy(['iso_code' => $iso_code]); - } else { - $candidates = $repo->findAll(); - } + $candidates = empty($iso_code) ? $repo->findAll() : $repo->findBy(['iso_code' => $iso_code]); $success_counter = 0; @@ -106,6 +90,6 @@ class UpdateExchangeRatesCommand extends Command $io->success(sprintf('%d (of %d) currency exchange rates were updated.', $success_counter, count($candidates))); - return 0; + return Command::SUCCESS; } } diff --git a/src/Command/Logs/ShowEventLogCommand.php b/src/Command/Logs/ShowEventLogCommand.php index 7eef7a2d..2d11b359 100644 --- a/src/Command/Logs/ShowEventLogCommand.php +++ b/src/Command/Logs/ShowEventLogCommand.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Command\Logs; +use Symfony\Component\Console\Attribute\AsCommand; +use App\Entity\UserSystem\User; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\LogSystem\AbstractLogEntry; use App\Repository\LogEntryRepository; @@ -36,23 +38,14 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Contracts\Translation\TranslatorInterface; +#[AsCommand('partdb:logs:show|app:show-logs', 'List the last event log entries.')] class ShowEventLogCommand extends Command { - protected static $defaultName = 'partdb:logs:show|app:show-logs'; - protected EntityManagerInterface $entityManager; - protected TranslatorInterface $translator; - protected ElementTypeNameGenerator $elementTypeNameGenerator; protected LogEntryRepository $repo; - protected LogEntryExtraFormatter $formatter; - public function __construct(EntityManagerInterface $entityManager, - TranslatorInterface $translator, ElementTypeNameGenerator $elementTypeNameGenerator, LogEntryExtraFormatter $formatter) + public function __construct(protected EntityManagerInterface $entityManager, + protected TranslatorInterface $translator, protected ElementTypeNameGenerator $elementTypeNameGenerator, protected LogEntryExtraFormatter $formatter) { - $this->entityManager = $entityManager; - $this->translator = $translator; - $this->elementTypeNameGenerator = $elementTypeNameGenerator; - $this->formatter = $formatter; - $this->repo = $this->entityManager->getRepository(AbstractLogEntry::class); parent::__construct(); } @@ -74,7 +67,7 @@ class ShowEventLogCommand extends Command if ($page > $max_page && $max_page > 0) { $io->error("There is no page ${page}! The maximum page is ${max_page}."); - return 1; + return Command::FAILURE; } $io->note("There are a total of ${total_count} log entries in the DB."); @@ -84,21 +77,19 @@ class ShowEventLogCommand extends Command $this->showPage($output, $desc, $limit, $page, $max_page, $showExtra); if ($onePage) { - return 0; + return Command::SUCCESS; } $continue = $io->confirm('Do you want to show the next page?'); ++$page; } - return 0; + return Command::SUCCESS; } protected function configure(): void { - $this - ->setDescription('List the last event log entries.') - ->addOption('count', 'c', InputOption::VALUE_REQUIRED, 'How many log entries should be shown per page.', 50) + $this->addOption('count', 'c', InputOption::VALUE_REQUIRED, 'How many log entries should be shown per page.', 50) ->addOption('oldest_first', null, InputOption::VALUE_NONE, 'Show older entries first.') ->addOption('page', 'p', InputOption::VALUE_REQUIRED, 'Which page should be shown?', 1) ->addOption('onePage', null, InputOption::VALUE_NONE, 'Show only one page (dont ask to go to next).') @@ -147,14 +138,12 @@ class ShowEventLogCommand extends Command $target_class = $this->elementTypeNameGenerator->getLocalizedTypeLabel($entry->getTargetClass()); } - if ($entry->getUser()) { + if ($entry->getUser() instanceof User) { $user = $entry->getUser()->getFullName(true); + } elseif ($entry->isCLIEntry()) { + $user = $entry->getCLIUsername() . ' [CLI]'; } else { - if ($entry->isCLIEntry()) { - $user = $entry->getCLIUsername() . ' [CLI]'; - } else { - $user = $entry->getUsername() . ' [deleted]'; - } + $user = $entry->getUsername() . ' [deleted]'; } $row = [ diff --git a/src/Command/Migrations/ConvertBBCodeCommand.php b/src/Command/Migrations/ConvertBBCodeCommand.php index 3b861b49..2297cbdd 100644 --- a/src/Command/Migrations/ConvertBBCodeCommand.php +++ b/src/Command/Migrations/ConvertBBCodeCommand.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Command\Migrations; +use Symfony\Component\Console\Attribute\AsCommand; use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\ProjectSystem\Project; @@ -47,6 +48,7 @@ use function count; /** * This command converts the BBCode used by old Part-DB versions (<1.0), to the current used Markdown format. */ +#[AsCommand('partdb:migrations:convert-bbcode|app:convert-bbcode', 'Converts BBCode used in old Part-DB versions to newly used Markdown')] class ConvertBBCodeCommand extends Command { /** @@ -57,18 +59,10 @@ class ConvertBBCodeCommand extends Command * @var string The regex (performed in PHP) used to check if a property really contains BBCODE */ protected const BBCODE_REGEX = '/\\[.+\\].*\\[\\/.+\\]/'; - - protected static $defaultName = 'partdb:migrations:convert-bbcode|app:convert-bbcode'; - - protected EntityManagerInterface $em; - protected PropertyAccessorInterface $propertyAccessor; protected BBCodeToMarkdownConverter $converter; - public function __construct(EntityManagerInterface $entityManager, PropertyAccessorInterface $propertyAccessor) + public function __construct(protected EntityManagerInterface $em, protected PropertyAccessorInterface $propertyAccessor) { - $this->em = $entityManager; - $this->propertyAccessor = $propertyAccessor; - $this->converter = new BBCodeToMarkdownConverter(); parent::__construct(); @@ -76,9 +70,7 @@ class ConvertBBCodeCommand extends Command protected function configure(): void { - $this - ->setDescription('Converts BBCode used in old Part-DB versions to newly used Markdown') - ->setHelp('Older versions of Part-DB (<1.0) used BBCode for rich text formatting. + $this->setHelp('Older versions of Part-DB (<1.0) used BBCode for rich text formatting. Part-DB now uses Markdown which offers more features but is incompatible with BBCode. When you upgrade from an pre 1.0 version you have to run this command to convert your comment fields'); @@ -129,25 +121,25 @@ class ConvertBBCodeCommand extends Command //Fetch resulting classes $results = $qb->getQuery()->getResult(); - $io->note(sprintf('Found %d entities, that need to be converted!', count($results))); + $io->note(sprintf('Found %d entities, that need to be converted!', is_countable($results) ? count($results) : 0)); //In verbose mode print the names of the entities foreach ($results as $result) { /** @var AbstractNamedDBElement $result */ $io->writeln( - 'Convert entity: '.$result->getName().' ('.get_class($result).': '.$result->getID().')', + 'Convert entity: '.$result->getName().' ('.$result::class.': '.$result->getID().')', OutputInterface::VERBOSITY_VERBOSE ); foreach ($properties as $property) { //Retrieve bbcode from entity $bbcode = $this->propertyAccessor->getValue($result, $property); //Check if the current property really contains BBCode - if (!preg_match(static::BBCODE_REGEX, $bbcode)) { + if (!preg_match(static::BBCODE_REGEX, (string) $bbcode)) { continue; } $io->writeln( 'BBCode (old): ' - .str_replace('\n', ' ', substr($bbcode, 0, 255)), + .str_replace('\n', ' ', substr((string) $bbcode, 0, 255)), OutputInterface::VERBOSITY_VERY_VERBOSE ); $markdown = $this->converter->convert($bbcode); @@ -168,6 +160,6 @@ class ConvertBBCodeCommand extends Command $io->success('Changes saved to DB successfully!'); } - return 0; + return Command::SUCCESS; } } diff --git a/src/Command/Migrations/ImportPartKeeprCommand.php b/src/Command/Migrations/ImportPartKeeprCommand.php index d436bb1c..98272440 100644 --- a/src/Command/Migrations/ImportPartKeeprCommand.php +++ b/src/Command/Migrations/ImportPartKeeprCommand.php @@ -1,4 +1,7 @@ . */ - namespace App\Command\Migrations; +use Symfony\Component\Console\Attribute\AsCommand; use App\Services\ImportExportSystem\PartKeeprImporter\PKDatastructureImporter; use App\Services\ImportExportSystem\PartKeeprImporter\MySQLDumpXMLConverter; use App\Services\ImportExportSystem\PartKeeprImporter\PKImportHelper; @@ -33,34 +36,19 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +#[AsCommand('partdb:migrations:import-partkeepr', 'Import a PartKeepr database XML dump into Part-DB')] class ImportPartKeeprCommand extends Command { - protected static $defaultName = 'partdb:migrations:import-partkeepr'; - - protected EntityManagerInterface $em; - protected MySQLDumpXMLConverter $xml_converter; - protected PKDatastructureImporter $datastructureImporter; - protected PKImportHelper $importHelper; - protected PKPartImporter $partImporter; - protected PKOptionalImporter $optionalImporter; - - public function __construct(EntityManagerInterface $em, MySQLDumpXMLConverter $xml_converter, - PKDatastructureImporter $datastructureImporter, PKPartImporter $partImporter, PKImportHelper $importHelper, - PKOptionalImporter $optionalImporter) + public function __construct(protected EntityManagerInterface $em, protected MySQLDumpXMLConverter $xml_converter, + protected PKDatastructureImporter $datastructureImporter, protected PKPartImporter $partImporter, protected PKImportHelper $importHelper, + protected PKOptionalImporter $optionalImporter) { parent::__construct(self::$defaultName); - $this->em = $em; - $this->datastructureImporter = $datastructureImporter; - $this->importHelper = $importHelper; - $this->partImporter = $partImporter; - $this->xml_converter = $xml_converter; - $this->optionalImporter = $optionalImporter; } - protected function configure() + protected function configure(): void { - $this->setDescription('Import a PartKeepr database XML dump into Part-DB'); $this->setHelp('This command allows you to import a PartKeepr database exported by mysqldump as XML file into Part-DB'); $this->addArgument('file', InputArgument::REQUIRED, 'The file to which should be imported.'); @@ -100,7 +88,7 @@ class ImportPartKeeprCommand extends Command if (!$this->importHelper->checkVersion($data)) { $db_version = $this->importHelper->getDatabaseSchemaVersion($data); $io->error('The version of the imported database is not supported! (Version: '.$db_version.')'); - return 1; + return Command::FAILURE; } //Import the mandatory data @@ -118,7 +106,7 @@ class ImportPartKeeprCommand extends Command $io->success('Imported '.$count.' users.'); } - return 0; + return Command::SUCCESS; } private function doImport(SymfonyStyle $io, array $data): void @@ -155,4 +143,4 @@ class ImportPartKeeprCommand extends Command $io->success('Imported '.$count.' parts.'); } -} \ No newline at end of file +} diff --git a/src/Command/User/ConvertToSAMLUserCommand.php b/src/Command/User/ConvertToSAMLUserCommand.php index df48ce06..98892ecd 100644 --- a/src/Command/User/ConvertToSAMLUserCommand.php +++ b/src/Command/User/ConvertToSAMLUserCommand.php @@ -1,4 +1,7 @@ . */ - namespace App\Command\User; +use Symfony\Component\Console\Attribute\AsCommand; use App\Entity\UserSystem\User; use App\Security\SamlUserFactory; use Doctrine\ORM\EntityManagerInterface; @@ -30,25 +33,17 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +#[AsCommand('partdb:user:convert-to-saml-user|partdb:users:convert-to-saml-user', 'Converts a local user to a SAML user (and vice versa)')] class ConvertToSAMLUserCommand extends Command { - protected static $defaultName = 'partdb:user:convert-to-saml-user|partdb:users:convert-to-saml-user'; - - protected EntityManagerInterface $entityManager; - protected bool $saml_enabled; - - public function __construct(EntityManagerInterface $entityManager, bool $saml_enabled) + public function __construct(protected EntityManagerInterface $entityManager, protected bool $saml_enabled) { parent::__construct(); - $this->entityManager = $entityManager; - $this->saml_enabled = $saml_enabled; } protected function configure(): void { - $this - ->setDescription('Converts a local user to a SAML user (and vice versa)') - ->setHelp('This converts a local user, which can login via the login form, to a SAML user, which can only login via SAML. This is useful if you want to migrate from a local user system to a SAML user system.') + $this->setHelp('This converts a local user, which can login via the login form, to a SAML user, which can only login via SAML. This is useful if you want to migrate from a local user system to a SAML user system.') ->addArgument('user', InputArgument::REQUIRED, 'The username (or email) of the user') ->addOption('to-local', null, InputOption::VALUE_NONE, 'Converts a SAML user to a local user') ; @@ -70,7 +65,7 @@ class ConvertToSAMLUserCommand extends Command if (!$user) { $io->error('User not found!'); - return 1; + return Command::FAILURE; } $io->info('User found: '.$user->getFullName(true) . ': '.$user->getEmail().' [ID: ' . $user->getID() . ']'); @@ -87,7 +82,7 @@ class ConvertToSAMLUserCommand extends Command $io->confirm('You are going to convert a SAML user to a local user. This means, that the user can only login via the login form. ' . 'The permissions and groups settings of the user will remain unchanged. Do you really want to continue?'); - $user->setSAMLUser(false); + $user->setSamlUser(false); $user->setPassword(SamlUserFactory::SAML_PASSWORD_PLACEHOLDER); $this->entityManager->flush(); @@ -102,7 +97,7 @@ class ConvertToSAMLUserCommand extends Command $io->confirm('You are going to convert a local user to a SAML user. This means, that the user can only login via SAML afterwards. The password in the DB will be removed. ' . 'The permissions and groups settings of the user will remain unchanged. Do you really want to continue?'); - $user->setSAMLUser(true); + $user->setSamlUser(true); $user->setPassword(SamlUserFactory::SAML_PASSWORD_PLACEHOLDER); $this->entityManager->flush(); @@ -112,4 +107,4 @@ class ConvertToSAMLUserCommand extends Command return 0; } -} \ No newline at end of file +} diff --git a/src/Command/User/SetPasswordCommand.php b/src/Command/User/SetPasswordCommand.php index 78724ecf..822277f1 100644 --- a/src/Command/User/SetPasswordCommand.php +++ b/src/Command/User/SetPasswordCommand.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Command\User; +use Symfony\Component\Console\Attribute\AsCommand; use App\Entity\UserSystem\User; use App\Events\SecurityEvent; use App\Events\SecurityEvents; @@ -34,28 +35,17 @@ use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; +#[AsCommand('partdb:users:set-password|app:set-password|users:set-password|partdb:user:set-password', 'Sets the password of a user')] class SetPasswordCommand extends Command { - protected static $defaultName = 'partdb:users:set-password|app:set-password|users:set-password|partdb:user:set-password'; - - protected EntityManagerInterface $entityManager; - protected UserPasswordHasherInterface $encoder; - protected EventDispatcherInterface $eventDispatcher; - - public function __construct(EntityManagerInterface $entityManager, UserPasswordHasherInterface $passwordEncoder, EventDispatcherInterface $eventDispatcher) + public function __construct(protected EntityManagerInterface $entityManager, protected UserPasswordHasherInterface $encoder, protected EventDispatcherInterface $eventDispatcher) { - $this->entityManager = $entityManager; - $this->encoder = $passwordEncoder; - $this->eventDispatcher = $eventDispatcher; - parent::__construct(); } protected function configure(): void { - $this - ->setDescription('Sets the password of a user') - ->setHelp('This password allows you to set the password of a user, without knowing the old password.') + $this->setHelp('This password allows you to set the password of a user, without knowing the old password.') ->addArgument('user', InputArgument::REQUIRED, 'The username or email of the user') ; } @@ -67,17 +57,17 @@ class SetPasswordCommand extends Command $user = $this->entityManager->getRepository(User::class)->findByEmailOrName($user_name); - if (!$user) { + if (!$user instanceof User) { $io->error(sprintf('No user with the given username %s found in the database!', $user_name)); - return 1; + return Command::FAILURE; } $io->note('User found!'); if ($user->isSamlUser()) { $io->error('This user is a SAML user, so you can not change the password!'); - return 1; + return Command::FAILURE; } $proceed = $io->confirm( @@ -85,7 +75,7 @@ class SetPasswordCommand extends Command $user->getFullName(true), $user->getID())); if (!$proceed) { - return 1; + return Command::FAILURE; } $success = false; @@ -116,6 +106,6 @@ class SetPasswordCommand extends Command $security_event = new SecurityEvent($user); $this->eventDispatcher->dispatch($security_event, SecurityEvents::PASSWORD_CHANGED); - return 0; + return Command::SUCCESS; } } diff --git a/src/Command/User/UpgradePermissionsSchemaCommand.php b/src/Command/User/UpgradePermissionsSchemaCommand.php index 36e933cc..4947fd5c 100644 --- a/src/Command/User/UpgradePermissionsSchemaCommand.php +++ b/src/Command/User/UpgradePermissionsSchemaCommand.php @@ -1,4 +1,7 @@ . */ - namespace App\Command\User; +use Symfony\Component\Console\Attribute\AsCommand; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\PermissionData; use App\Entity\UserSystem\User; @@ -31,22 +34,12 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +#[AsCommand('partdb:users:upgrade-permissions-schema', '(Manually) upgrades the permissions schema of all users to the latest version.')] final class UpgradePermissionsSchemaCommand extends Command { - protected static $defaultName = 'partdb:users:upgrade-permissions-schema'; - protected static $defaultDescription = '(Manually) upgrades the permissions schema of all users to the latest version.'; - - private PermissionSchemaUpdater $permissionSchemaUpdater; - private EntityManagerInterface $em; - private EventCommentHelper $eventCommentHelper; - - public function __construct(PermissionSchemaUpdater $permissionSchemaUpdater, EntityManagerInterface $entityManager, EventCommentHelper $eventCommentHelper) + public function __construct(private readonly PermissionSchemaUpdater $permissionSchemaUpdater, private readonly EntityManagerInterface $em, private readonly EventCommentHelper $eventCommentHelper) { parent::__construct(self::$defaultName); - - $this->permissionSchemaUpdater = $permissionSchemaUpdater; - $this->eventCommentHelper = $eventCommentHelper; - $this->em = $entityManager; } protected function configure(): void @@ -81,26 +74,22 @@ final class UpgradePermissionsSchemaCommand extends Command } $io->info('Found '. count($groups_to_upgrade) .' groups and '. count($users_to_upgrade) .' users that need an update.'); - if (empty($groups_to_upgrade) && empty($users_to_upgrade)) { + if ($groups_to_upgrade === [] && $users_to_upgrade === []) { $io->success('All users and group permissions schemas are up-to-date. No update needed.'); - return 0; + return Command::SUCCESS; } //List all users and groups that need an update $io->section('Groups that need an update:'); - $io->listing(array_map(static function (Group $group) { - return $group->getName() . ' (ID: '. $group->getID() .', Current version: ' . $group->getPermissions()->getSchemaVersion() . ')'; - }, $groups_to_upgrade)); + $io->listing(array_map(static fn(Group $group): string => $group->getName() . ' (ID: '. $group->getID() .', Current version: ' . $group->getPermissions()->getSchemaVersion() . ')', $groups_to_upgrade)); $io->section('Users that need an update:'); - $io->listing(array_map(static function (User $user) { - return $user->getUsername() . ' (ID: '. $user->getID() .', Current version: ' . $user->getPermissions()->getSchemaVersion() . ')'; - }, $users_to_upgrade)); + $io->listing(array_map(static fn(User $user): string => $user->getUsername() . ' (ID: '. $user->getID() .', Current version: ' . $user->getPermissions()->getSchemaVersion() . ')', $users_to_upgrade)); if(!$io->confirm('Continue with the update?', false)) { $io->warning('Update aborted.'); - return 0; + return Command::SUCCESS; } //Update all users and groups diff --git a/src/Command/User/UserEnableCommand.php b/src/Command/User/UserEnableCommand.php index 2b913e26..00753e94 100644 --- a/src/Command/User/UserEnableCommand.php +++ b/src/Command/User/UserEnableCommand.php @@ -1,4 +1,7 @@ . */ - namespace App\Command\User; +use Symfony\Component\Console\Attribute\AsCommand; use App\Entity\UserSystem\User; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Command\Command; @@ -29,24 +32,17 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +#[AsCommand('partdb:users:enable|partdb:user:enable', 'Enables/Disable the login of one or more users')] class UserEnableCommand extends Command { - protected static $defaultName = 'partdb:users:enable|partdb:user:enable'; - - protected EntityManagerInterface $entityManager; - - public function __construct(EntityManagerInterface $entityManager, string $name = null) + public function __construct(protected EntityManagerInterface $entityManager, string $name = null) { - $this->entityManager = $entityManager; - parent::__construct($name); } protected function configure(): void { - $this - ->setDescription('Enables/Disable the login of one or more users') - ->setHelp('This allows you to allow or prevent the login of certain user. Use the --disable option to disable the login for the given users') + $this->setHelp('This allows you to allow or prevent the login of certain user. Use the --disable option to disable the login for the given users') ->addArgument('users', InputArgument::IS_ARRAY, 'The usernames of the users to use') ->addOption('all', 'a', InputOption::VALUE_NONE, 'Enable/Disable all users') ->addOption('disable', 'd', InputOption::VALUE_NONE, 'Disable the login of the given users') @@ -73,7 +69,7 @@ class UserEnableCommand extends Command } else { //Otherwise, fetch the users from DB foreach ($usernames as $username) { $user = $repo->findByEmailOrName($username); - if ($user === null) { + if (!$user instanceof User) { $io->error('No user found with username: '.$username); return self::FAILURE; } @@ -87,9 +83,7 @@ class UserEnableCommand extends Command $io->note('The following users will be enabled:'); } $io->table(['Username', 'Enabled/Disabled'], - array_map(static function(User $user) { - return [$user->getFullName(true), $user->isDisabled() ? 'Disabled' : 'Enabled']; - }, $users)); + array_map(static fn(User $user) => [$user->getFullName(true), $user->isDisabled() ? 'Disabled' : 'Enabled'], $users)); if(!$io->confirm('Do you want to continue?')) { $io->warning('Aborting!'); @@ -107,4 +101,4 @@ class UserEnableCommand extends Command return self::SUCCESS; } -} \ No newline at end of file +} diff --git a/src/Command/User/UserListCommand.php b/src/Command/User/UserListCommand.php index 66265dd8..11d50fbf 100644 --- a/src/Command/User/UserListCommand.php +++ b/src/Command/User/UserListCommand.php @@ -1,4 +1,7 @@ . */ - namespace App\Command\User; +use Symfony\Component\Console\Attribute\AsCommand; +use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Command\Command; @@ -28,24 +32,17 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +#[AsCommand('partdb:users:list|users:list', 'Lists all users')] class UserListCommand extends Command { - protected static $defaultName = 'partdb:users:list|users:list'; - - protected EntityManagerInterface $entityManager; - - public function __construct(EntityManagerInterface $entityManager) + public function __construct(protected EntityManagerInterface $entityManager) { - $this->entityManager = $entityManager; - parent::__construct(); } protected function configure(): void { - $this - ->setDescription('Lists all users') - ->setHelp('This command lists all users in the database.') + $this->setHelp('This command lists all users in the database.') ->addOption('local', 'l', null, 'Only list local users') ->addOption('saml', 's', null, 'Only list SAML users') ; @@ -82,13 +79,13 @@ class UserListCommand extends Command foreach ($users as $user) { $table->addRow([ - $user->getId(), + $user->getID(), $user->getUsername(), $user->getFullName(), $user->getEmail(), - $user->getGroup() !== null ? $user->getGroup()->getName() . ' (ID: ' . $user->getGroup()->getID() . ')' : 'No group', + $user->getGroup() instanceof Group ? $user->getGroup()->getName() . ' (ID: ' . $user->getGroup()->getID() . ')' : 'No group', $user->isDisabled() ? 'Yes' : 'No', - $user->isSAMLUser() ? 'SAML' : 'Local', + $user->isSamlUser() ? 'SAML' : 'Local', ]); } @@ -98,4 +95,4 @@ class UserListCommand extends Command return self::SUCCESS; } -} \ No newline at end of file +} diff --git a/src/Command/User/UsersPermissionsCommand.php b/src/Command/User/UsersPermissionsCommand.php index 3d57a4dc..021853bb 100644 --- a/src/Command/User/UsersPermissionsCommand.php +++ b/src/Command/User/UsersPermissionsCommand.php @@ -1,4 +1,7 @@ . */ - namespace App\Command\User; +use Symfony\Component\Console\Attribute\AsCommand; use App\Entity\UserSystem\User; use App\Repository\UserRepository; use App\Services\UserSystem\PermissionManager; @@ -34,22 +37,14 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use Symfony\Contracts\Translation\TranslatorInterface; +#[AsCommand('partdb:users:permissions|partdb:user:permissions', 'View and edit the permissions of a given user')] class UsersPermissionsCommand extends Command { - protected static $defaultName = 'partdb:users:permissions|partdb:user:permissions'; - protected static $defaultDescription = 'View and edit the permissions of a given user'; - - protected EntityManagerInterface $entityManager; protected UserRepository $userRepository; - protected PermissionManager $permissionResolver; - protected TranslatorInterface $translator; - public function __construct(EntityManagerInterface $entityManager, PermissionManager $permissionResolver, TranslatorInterface $translator) + public function __construct(protected EntityManagerInterface $entityManager, protected PermissionManager $permissionResolver, protected TranslatorInterface $translator) { - $this->entityManager = $entityManager; $this->userRepository = $entityManager->getRepository(User::class); - $this->permissionResolver = $permissionResolver; - $this->translator = $translator; parent::__construct(self::$defaultName); } @@ -73,12 +68,12 @@ class UsersPermissionsCommand extends Command //Find user $io->note('Finding user with username: ' . $username); $user = $this->userRepository->findByEmailOrName($username); - if ($user === null) { + if (!$user instanceof User) { $io->error('No user found with username: ' . $username); return Command::FAILURE; } - $io->note(sprintf('Found user %s with ID %d', $user->getFullName(true), $user->getId())); + $io->note(sprintf('Found user %s with ID %d', $user->getFullName(true), $user->getID())); $edit_mapping = $this->renderPermissionTable($output, $user, $inherit); @@ -102,7 +97,7 @@ class UsersPermissionsCommand extends Command $new_value_str = $io->ask('Enter the new value for the permission (A = allow, D = disallow, I = inherit)'); - switch (strtolower($new_value_str)) { + switch (strtolower((string) $new_value_str)) { case 'a': case 'allow': $new_value = true; @@ -209,11 +204,11 @@ class UsersPermissionsCommand extends Command if ($permission_value === true) { return 'Allow'; - } else if ($permission_value === false) { + } elseif ($permission_value === false) { return 'Disallow'; - } else if ($permission_value === null && !$inherit) { + } elseif ($permission_value === null && !$inherit) { return 'Inherit'; - } else if ($permission_value === null && $inherit) { + } elseif ($permission_value === null && $inherit) { return 'Disallow (Inherited)'; } diff --git a/src/Command/VersionCommand.php b/src/Command/VersionCommand.php index a5437684..d2ce75e1 100644 --- a/src/Command/VersionCommand.php +++ b/src/Command/VersionCommand.php @@ -1,4 +1,7 @@ . */ - namespace App\Command; +use Symfony\Component\Console\Attribute\AsCommand; use App\Services\Misc\GitVersionInfo; use Shivas\VersioningBundle\Service\VersionManagerInterface; use Symfony\Component\Console\Command\Command; @@ -27,25 +30,16 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +#[AsCommand('partdb:version|app:version', 'Shows the currently installed version of Part-DB.')] class VersionCommand extends Command { - protected static $defaultName = 'partdb:version|app:version'; - - protected VersionManagerInterface $versionManager; - protected GitVersionInfo $gitVersionInfo; - - public function __construct(VersionManagerInterface $versionManager, GitVersionInfo $gitVersionInfo) + public function __construct(protected VersionManagerInterface $versionManager, protected GitVersionInfo $gitVersionInfo) { - $this->versionManager = $versionManager; - $this->gitVersionInfo = $gitVersionInfo; parent::__construct(); } protected function configure(): void { - $this - ->setDescription('Shows the currently installed version of Part-DB.') - ; } protected function execute(InputInterface $input, OutputInterface $output): int @@ -66,6 +60,6 @@ class VersionCommand extends Command $io->info('OS: '. php_uname()); $io->info('PHP extension: '. implode(', ', get_loaded_extensions())); - return 0; + return Command::SUCCESS; } -} \ No newline at end of file +} diff --git a/src/Controller/AdminPages/AttachmentTypeController.php b/src/Controller/AdminPages/AttachmentTypeController.php index 6ff5d930..426e773c 100644 --- a/src/Controller/AdminPages/AttachmentTypeController.php +++ b/src/Controller/AdminPages/AttachmentTypeController.php @@ -37,8 +37,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; /** - * @Route("/attachment_type") + * @see \App\Tests\Controller\AdminPages\AttachmentTypeControllerTest */ +#[Route(path: '/attachment_type')] class AttachmentTypeController extends BaseAdminController { protected string $entity_class = AttachmentType::class; @@ -48,44 +49,34 @@ class AttachmentTypeController extends BaseAdminController protected string $attachment_class = AttachmentTypeAttachment::class; protected ?string $parameter_class = AttachmentTypeParameter::class; - /** - * @Route("/{id}", name="attachment_type_delete", methods={"DELETE"}) - */ + #[Route(path: '/{id}', name: 'attachment_type_delete', methods: ['DELETE'])] public function delete(Request $request, AttachmentType $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { return $this->_delete($request, $entity, $recursionHelper); } - /** - * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="attachment_type_edit") - * @Route("/{id}", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'attachment_type_edit')] + #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(AttachmentType $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { return $this->_edit($entity, $request, $em, $timestamp); } - /** - * @Route("/new", name="attachment_type_new") - * @Route("/{id}/clone", name="attachment_type_clone") - * @Route("/") - */ + #[Route(path: '/new', name: 'attachment_type_new')] + #[Route(path: '/{id}/clone', name: 'attachment_type_clone')] + #[Route(path: '/')] public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AttachmentType $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } - /** - * @Route("/export", name="attachment_type_export_all") - */ + #[Route(path: '/export', name: 'attachment_type_export_all')] public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response { return $this->_exportAll($em, $exporter, $request); } - /** - * @Route("/{id}/export", name="attachment_type_export") - */ + #[Route(path: '/{id}/export', name: 'attachment_type_export')] public function exportEntity(AttachmentType $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); diff --git a/src/Controller/AdminPages/BaseAdminController.php b/src/Controller/AdminPages/BaseAdminController.php index d5f00767..9f43d07d 100644 --- a/src/Controller/AdminPages/BaseAdminController.php +++ b/src/Controller/AdminPages/BaseAdminController.php @@ -29,6 +29,7 @@ use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractPartsContainingDBElement; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Base\PartsContainingRepositoryInterface; +use App\Entity\LabelSystem\LabelProcessMode; use App\Entity\LabelSystem\LabelProfile; use App\Entity\Parameters\AbstractParameter; use App\Entity\UserSystem\User; @@ -72,29 +73,16 @@ abstract class BaseAdminController extends AbstractController protected string $route_base = ''; protected string $attachment_class = ''; protected ?string $parameter_class = ''; - - protected UserPasswordHasherInterface $passwordEncoder; - protected TranslatorInterface $translator; - protected AttachmentSubmitHandler $attachmentSubmitHandler; - protected EventCommentHelper $commentHelper; - - protected HistoryHelper $historyHelper; - protected TimeTravel $timeTravel; - protected DataTableFactory $dataTableFactory; /** * @var EventDispatcher|EventDispatcherInterface */ protected $eventDispatcher; - protected LabelGenerator $labelGenerator; - protected LabelExampleElementsGenerator $barcodeExampleGenerator; - protected EntityManagerInterface $entityManager; - - public function __construct(TranslatorInterface $translator, UserPasswordHasherInterface $passwordEncoder, - AttachmentSubmitHandler $attachmentSubmitHandler, - EventCommentHelper $commentHelper, HistoryHelper $historyHelper, TimeTravel $timeTravel, - DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, LabelExampleElementsGenerator $barcodeExampleGenerator, - LabelGenerator $labelGenerator, EntityManagerInterface $entityManager) + public function __construct(protected TranslatorInterface $translator, protected UserPasswordHasherInterface $passwordEncoder, + protected AttachmentSubmitHandler $attachmentSubmitHandler, + protected EventCommentHelper $commentHelper, protected HistoryHelper $historyHelper, protected TimeTravel $timeTravel, + protected DataTableFactory $dataTableFactory, EventDispatcherInterface $eventDispatcher, protected LabelExampleElementsGenerator $barcodeExampleGenerator, + protected LabelGenerator $labelGenerator, protected EntityManagerInterface $entityManager) { if ('' === $this->entity_class || '' === $this->form_class || '' === $this->twig_template || '' === $this->route_base) { throw new InvalidArgumentException('You have to override the $entity_class, $form_class, $route_base and $twig_template value in your subclasss!'); @@ -107,18 +95,7 @@ abstract class BaseAdminController extends AbstractController if ('' === $this->parameter_class || ($this->parameter_class && !is_a($this->parameter_class, AbstractParameter::class, true))) { throw new InvalidArgumentException('You have to override the $parameter_class value with a valid Parameter class in your subclass!'); } - - $this->translator = $translator; - $this->passwordEncoder = $passwordEncoder; - $this->attachmentSubmitHandler = $attachmentSubmitHandler; - $this->commentHelper = $commentHelper; - $this->historyHelper = $historyHelper; - $this->timeTravel = $timeTravel; - $this->dataTableFactory = $dataTableFactory; $this->eventDispatcher = $eventDispatcher; - $this->barcodeExampleGenerator = $barcodeExampleGenerator; - $this->labelGenerator = $labelGenerator; - $this->entityManager = $entityManager; } protected function revertElementIfNeeded(AbstractDBElement $entity, ?string $timestamp): ?DateTime @@ -177,13 +154,13 @@ abstract class BaseAdminController extends AbstractController $form_options = [ 'attachment_class' => $this->attachment_class, 'parameter_class' => $this->parameter_class, - 'disabled' => null !== $timeTravel_timestamp, + 'disabled' => $timeTravel_timestamp instanceof \DateTime, ]; //Disable editing of options, if user is not allowed to use twig... if ( $entity instanceof LabelProfile - && 'twig' === $entity->getOptions()->getLinesMode() + && LabelProcessMode::TWIG === $entity->getOptions()->getProcessMode() && !$this->isGranted('@labels.use_twig') ) { $form_options['disable_options'] = true; @@ -245,7 +222,7 @@ abstract class BaseAdminController extends AbstractController /** @var AbstractPartsContainingRepository $repo */ $repo = $this->entityManager->getRepository($this->entity_class); - return $this->renderForm($this->twig_template, [ + return $this->render($this->twig_template, [ 'entity' => $entity, 'form' => $form, 'route_base' => $this->route_base, @@ -267,15 +244,9 @@ abstract class BaseAdminController extends AbstractController return true; } - protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AbstractNamedDBElement $entity = null) + protected function _new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?AbstractNamedDBElement $entity = null): Response { - if (null === $entity) { - /** @var AbstractStructuralDBElement|User $new_entity */ - $new_entity = new $this->entity_class(); - } else { - /** @var AbstractStructuralDBElement|User $new_entity */ - $new_entity = clone $entity; - } + $new_entity = $entity instanceof AbstractNamedDBElement ? clone $entity : new $this->entity_class(); $this->denyAccessUnlessGranted('read', $new_entity); @@ -287,42 +258,37 @@ abstract class BaseAdminController extends AbstractController $form->handleRequest($request); - if ($form->isSubmitted() && $form->isValid()) { - //Perform additional actions - if ($this->additionalActionNew($form, $new_entity)) { - //Upload passed files - $attachments = $form['attachments']; - foreach ($attachments as $attachment) { - /** @var FormInterface $attachment */ - $options = [ - 'secure_attachment' => $attachment['secureFile']->getData(), - 'download_url' => $attachment['downloadURL']->getData(), - ]; + //Perform additional actions + if ($form->isSubmitted() && $form->isValid() && $this->additionalActionNew($form, $new_entity)) { + //Upload passed files + $attachments = $form['attachments']; + foreach ($attachments as $attachment) { + /** @var FormInterface $attachment */ + $options = [ + 'secure_attachment' => $attachment['secureFile']->getData(), + 'download_url' => $attachment['downloadURL']->getData(), + ]; - try { - $this->attachmentSubmitHandler->handleFormSubmit( - $attachment->getData(), - $attachment['file']->getData(), - $options - ); - } catch (AttachmentDownloadException $attachmentDownloadException) { - $this->addFlash( - 'error', - $this->translator->trans( - 'attachment.download_failed' - ).' '.$attachmentDownloadException->getMessage() - ); - } + try { + $this->attachmentSubmitHandler->handleFormSubmit( + $attachment->getData(), + $attachment['file']->getData(), + $options + ); + } catch (AttachmentDownloadException $attachmentDownloadException) { + $this->addFlash( + 'error', + $this->translator->trans( + 'attachment.download_failed' + ).' '.$attachmentDownloadException->getMessage() + ); } - - $this->commentHelper->setMessage($form['log_comment']->getData()); - - $em->persist($new_entity); - $em->flush(); - $this->addFlash('success', 'entity.created_flash'); - - return $this->redirectToRoute($this->route_base.'_edit', ['id' => $new_entity->getID()]); } + $this->commentHelper->setMessage($form['log_comment']->getData()); + $em->persist($new_entity); + $em->flush(); + $this->addFlash('success', 'entity.created_flash'); + return $this->redirectToRoute($this->route_base.'_edit', ['id' => $new_entity->getID()]); } if ($form->isSubmitted() && !$form->isValid()) { @@ -362,14 +328,13 @@ abstract class BaseAdminController extends AbstractController try { $errors = $importer->importFileAndPersistToDB($file, $options); - /** @var ConstraintViolationList $error */ foreach ($errors as $name => $error) { - foreach ($error['violations'] as $violation) { + foreach ($error as $violation) { $this->addFlash('error', $name.': '.$violation->getMessage()); } } } - catch (UnexpectedValueException $e) { + catch (UnexpectedValueException) { $this->addFlash('error', 'parts.import.flash.error.invalid_file'); } } @@ -402,7 +367,7 @@ abstract class BaseAdminController extends AbstractController } ret: - return $this->renderForm($this->twig_template, [ + return $this->render($this->twig_template, [ 'entity' => $new_entity, 'form' => $form, 'import_form' => $import_form, @@ -437,7 +402,7 @@ abstract class BaseAdminController extends AbstractController { $this->denyAccessUnlessGranted('delete', $entity); - if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) { + if ($this->isCsrfTokenValid('delete'.$entity->getID(), $request->request->get('_token'))) { $entityManager = $this->entityManager; diff --git a/src/Controller/AdminPages/CategoryController.php b/src/Controller/AdminPages/CategoryController.php index 4f247a1d..153d46f3 100644 --- a/src/Controller/AdminPages/CategoryController.php +++ b/src/Controller/AdminPages/CategoryController.php @@ -36,8 +36,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; /** - * @Route("/category") + * @see \App\Tests\Controller\AdminPages\CategoryControllerTest */ +#[Route(path: '/category')] class CategoryController extends BaseAdminController { protected string $entity_class = Category::class; @@ -47,44 +48,34 @@ class CategoryController extends BaseAdminController protected string $attachment_class = CategoryAttachment::class; protected ?string $parameter_class = CategoryParameter::class; - /** - * @Route("/{id}", name="category_delete", methods={"DELETE"}) - */ + #[Route(path: '/{id}', name: 'category_delete', methods: ['DELETE'])] public function delete(Request $request, Category $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { return $this->_delete($request, $entity, $recursionHelper); } - /** - * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="category_edit") - * @Route("/{id}", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'category_edit')] + #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(Category $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { return $this->_edit($entity, $request, $em, $timestamp); } - /** - * @Route("/new", name="category_new") - * @Route("/{id}/clone", name="category_clone") - * @Route("/") - */ + #[Route(path: '/new', name: 'category_new')] + #[Route(path: '/{id}/clone', name: 'category_clone')] + #[Route(path: '/')] public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Category $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } - /** - * @Route("/export", name="category_export_all") - */ + #[Route(path: '/export', name: 'category_export_all')] public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response { return $this->_exportAll($em, $exporter, $request); } - /** - * @Route("/{id}/export", name="category_export") - */ + #[Route(path: '/{id}/export', name: 'category_export')] public function exportEntity(Category $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); diff --git a/src/Controller/AdminPages/CurrencyController.php b/src/Controller/AdminPages/CurrencyController.php index bda992c5..18d449fd 100644 --- a/src/Controller/AdminPages/CurrencyController.php +++ b/src/Controller/AdminPages/CurrencyController.php @@ -52,10 +52,9 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Contracts\Translation\TranslatorInterface; /** - * @Route("/currency") - * * Class CurrencyController */ +#[Route(path: '/currency')] class CurrencyController extends BaseAdminController { protected string $entity_class = Currency::class; @@ -65,8 +64,6 @@ class CurrencyController extends BaseAdminController protected string $attachment_class = CurrencyAttachment::class; protected ?string $parameter_class = CurrencyParameter::class; - protected ExchangeRateUpdater $exchangeRateUpdater; - public function __construct( TranslatorInterface $translator, UserPasswordHasherInterface $passwordEncoder, @@ -79,10 +76,8 @@ class CurrencyController extends BaseAdminController LabelExampleElementsGenerator $barcodeExampleGenerator, LabelGenerator $labelGenerator, EntityManagerInterface $entityManager, - ExchangeRateUpdater $exchangeRateUpdater + protected ExchangeRateUpdater $exchangeRateUpdater ) { - $this->exchangeRateUpdater = $exchangeRateUpdater; - parent::__construct( $translator, $passwordEncoder, @@ -98,9 +93,7 @@ class CurrencyController extends BaseAdminController ); } - /** - * @Route("/{id}", name="currency_delete", methods={"DELETE"}) - */ + #[Route(path: '/{id}', name: 'currency_delete', methods: ['DELETE'])] public function delete(Request $request, Currency $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { return $this->_delete($request, $entity, $recursionHelper); @@ -131,36 +124,28 @@ class CurrencyController extends BaseAdminController return true; } - /** - * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="currency_edit") - * @Route("/{id}", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'currency_edit')] + #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(Currency $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { return $this->_edit($entity, $request, $em, $timestamp); } - /** - * @Route("/new", name="currency_new") - * @Route("/{id}/clone", name="currency_clone") - * @Route("/") - */ + #[Route(path: '/new', name: 'currency_new')] + #[Route(path: '/{id}/clone', name: 'currency_clone')] + #[Route(path: '/')] public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Currency $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } - /** - * @Route("/export", name="currency_export_all") - */ + #[Route(path: '/export', name: 'currency_export_all')] public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response { return $this->_exportAll($em, $exporter, $request); } - /** - * @Route("/{id}/export", name="currency_export") - */ + #[Route(path: '/{id}/export', name: 'currency_export')] public function exportEntity(Currency $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); diff --git a/src/Controller/AdminPages/FootprintController.php b/src/Controller/AdminPages/FootprintController.php index ba37256c..d1414d2a 100644 --- a/src/Controller/AdminPages/FootprintController.php +++ b/src/Controller/AdminPages/FootprintController.php @@ -37,8 +37,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; /** - * @Route("/footprint") + * @see \App\Tests\Controller\AdminPages\FootprintControllerTest */ +#[Route(path: '/footprint')] class FootprintController extends BaseAdminController { protected string $entity_class = Footprint::class; @@ -48,44 +49,34 @@ class FootprintController extends BaseAdminController protected string $attachment_class = FootprintAttachment::class; protected ?string $parameter_class = FootprintParameter::class; - /** - * @Route("/{id}", name="footprint_delete", methods={"DELETE"}) - */ + #[Route(path: '/{id}', name: 'footprint_delete', methods: ['DELETE'])] public function delete(Request $request, Footprint $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { return $this->_delete($request, $entity, $recursionHelper); } - /** - * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="footprint_edit") - * @Route("/{id}", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'footprint_edit')] + #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(Footprint $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { return $this->_edit($entity, $request, $em, $timestamp); } - /** - * @Route("/new", name="footprint_new") - * @Route("/{id}/clone", name="footprint_clone") - * @Route("/") - */ + #[Route(path: '/new', name: 'footprint_new')] + #[Route(path: '/{id}/clone', name: 'footprint_clone')] + #[Route(path: '/')] public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Footprint $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } - /** - * @Route("/export", name="footprint_export_all") - */ + #[Route(path: '/export', name: 'footprint_export_all')] public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response { return $this->_exportAll($em, $exporter, $request); } - /** - * @Route("/{id}/export", name="footprint_export") - */ + #[Route(path: '/{id}/export', name: 'footprint_export')] public function exportEntity(AttachmentType $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); diff --git a/src/Controller/AdminPages/LabelProfileController.php b/src/Controller/AdminPages/LabelProfileController.php index 1b3579db..ee754436 100644 --- a/src/Controller/AdminPages/LabelProfileController.php +++ b/src/Controller/AdminPages/LabelProfileController.php @@ -36,8 +36,9 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; /** - * @Route("/label_profile") + * @see \App\Tests\Controller\AdminPages\LabelProfileControllerTest */ +#[Route(path: '/label_profile')] class LabelProfileController extends BaseAdminController { protected string $entity_class = LabelProfile::class; @@ -48,44 +49,34 @@ class LabelProfileController extends BaseAdminController //Just a placeholder protected ?string $parameter_class = null; - /** - * @Route("/{id}", name="label_profile_delete", methods={"DELETE"}) - */ + #[Route(path: '/{id}', name: 'label_profile_delete', methods: ['DELETE'])] public function delete(Request $request, LabelProfile $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { return $this->_delete($request, $entity, $recursionHelper); } - /** - * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="label_profile_edit") - * @Route("/{id}", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'label_profile_edit')] + #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(LabelProfile $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { return $this->_edit($entity, $request, $em, $timestamp); } - /** - * @Route("/new", name="label_profile_new") - * @Route("/{id}/clone", name="label_profile_clone") - * @Route("/") - */ + #[Route(path: '/new', name: 'label_profile_new')] + #[Route(path: '/{id}/clone', name: 'label_profile_clone')] + #[Route(path: '/')] public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?LabelProfile $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } - /** - * @Route("/export", name="label_profile_export_all") - */ + #[Route(path: '/export', name: 'label_profile_export_all')] public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response { return $this->_exportAll($em, $exporter, $request); } - /** - * @Route("/{id}/export", name="label_profile_export") - */ + #[Route(path: '/{id}/export', name: 'label_profile_export')] public function exportEntity(LabelProfile $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); diff --git a/src/Controller/AdminPages/ManufacturerController.php b/src/Controller/AdminPages/ManufacturerController.php index 2ded7d10..2a97d3f3 100644 --- a/src/Controller/AdminPages/ManufacturerController.php +++ b/src/Controller/AdminPages/ManufacturerController.php @@ -36,8 +36,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; /** - * @Route("/manufacturer") + * @see \App\Tests\Controller\AdminPages\ManufacturerControllerTest */ +#[Route(path: '/manufacturer')] class ManufacturerController extends BaseAdminController { protected string $entity_class = Manufacturer::class; @@ -47,46 +48,34 @@ class ManufacturerController extends BaseAdminController protected string $attachment_class = ManufacturerAttachment::class; protected ?string $parameter_class = ManufacturerParameter::class; - /** - * @Route("/{id}", name="manufacturer_delete", methods={"DELETE"}) - * - * @return RedirectResponse - */ + #[Route(path: '/{id}', name: 'manufacturer_delete', methods: ['DELETE'])] public function delete(Request $request, Manufacturer $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { return $this->_delete($request, $entity, $recursionHelper); } - /** - * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="manufacturer_edit") - * @Route("/{id}", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'manufacturer_edit')] + #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(Manufacturer $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { return $this->_edit($entity, $request, $em, $timestamp); } - /** - * @Route("/new", name="manufacturer_new") - * @Route("/{id}/clone", name="manufacturer_clone") - * @Route("/") - */ + #[Route(path: '/new', name: 'manufacturer_new')] + #[Route(path: '/{id}/clone', name: 'manufacturer_clone')] + #[Route(path: '/')] public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Manufacturer $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } - /** - * @Route("/export", name="manufacturer_export_all") - */ + #[Route(path: '/export', name: 'manufacturer_export_all')] public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response { return $this->_exportAll($em, $exporter, $request); } - /** - * @Route("/{id}/export", name="manufacturer_export") - */ + #[Route(path: '/{id}/export', name: 'manufacturer_export')] public function exportEntity(Manufacturer $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); diff --git a/src/Controller/AdminPages/MeasurementUnitController.php b/src/Controller/AdminPages/MeasurementUnitController.php index 402c2018..993c5dad 100644 --- a/src/Controller/AdminPages/MeasurementUnitController.php +++ b/src/Controller/AdminPages/MeasurementUnitController.php @@ -37,8 +37,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; /** - * @Route("/measurement_unit") + * @see \App\Tests\Controller\AdminPages\MeasurementUnitControllerTest */ +#[Route(path: '/measurement_unit')] class MeasurementUnitController extends BaseAdminController { protected string $entity_class = MeasurementUnit::class; @@ -48,44 +49,34 @@ class MeasurementUnitController extends BaseAdminController protected string $attachment_class = MeasurementUnitAttachment::class; protected ?string $parameter_class = MeasurementUnitParameter::class; - /** - * @Route("/{id}", name="measurement_unit_delete", methods={"DELETE"}) - */ + #[Route(path: '/{id}', name: 'measurement_unit_delete', methods: ['DELETE'])] public function delete(Request $request, MeasurementUnit $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { return $this->_delete($request, $entity, $recursionHelper); } - /** - * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="measurement_unit_edit") - * @Route("/{id}", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'measurement_unit_edit')] + #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(MeasurementUnit $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { return $this->_edit($entity, $request, $em, $timestamp); } - /** - * @Route("/new", name="measurement_unit_new") - * @Route("/{id}/clone", name="measurement_unit_clone") - * @Route("/") - */ + #[Route(path: '/new', name: 'measurement_unit_new')] + #[Route(path: '/{id}/clone', name: 'measurement_unit_clone')] + #[Route(path: '/')] public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?MeasurementUnit $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } - /** - * @Route("/export", name="measurement_unit_export_all") - */ + #[Route(path: '/export', name: 'measurement_unit_export_all')] public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response { return $this->_exportAll($em, $exporter, $request); } - /** - * @Route("/{id}/export", name="measurement_unit_export") - */ + #[Route(path: '/{id}/export', name: 'measurement_unit_export')] public function exportEntity(AttachmentType $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); diff --git a/src/Controller/AdminPages/ProjectAdminController.php b/src/Controller/AdminPages/ProjectAdminController.php index 08d088ec..16bf6df1 100644 --- a/src/Controller/AdminPages/ProjectAdminController.php +++ b/src/Controller/AdminPages/ProjectAdminController.php @@ -35,9 +35,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; -/** - * @Route("/project") - */ +#[Route(path: '/project')] class ProjectAdminController extends BaseAdminController { protected string $entity_class = Project::class; @@ -47,44 +45,34 @@ class ProjectAdminController extends BaseAdminController protected string $attachment_class = ProjectAttachment::class; protected ?string $parameter_class = ProjectParameter::class; - /** - * @Route("/{id}", name="project_delete", methods={"DELETE"}) - */ + #[Route(path: '/{id}', name: 'project_delete', methods: ['DELETE'])] public function delete(Request $request, Project $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { return $this->_delete($request, $entity, $recursionHelper); } - /** - * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="project_edit") - * @Route("/{id}/edit", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'project_edit')] + #[Route(path: '/{id}/edit', requirements: ['id' => '\d+'])] public function edit(Project $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { return $this->_edit($entity, $request, $em, $timestamp); } - /** - * @Route("/new", name="project_new") - * @Route("/{id}/clone", name="device_clone") - * @Route("/") - */ + #[Route(path: '/new', name: 'project_new')] + #[Route(path: '/{id}/clone', name: 'device_clone')] + #[Route(path: '/')] public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Project $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } - /** - * @Route("/export", name="project_export_all") - */ + #[Route(path: '/export', name: 'project_export_all')] public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response { return $this->_exportAll($em, $exporter, $request); } - /** - * @Route("/{id}/export", name="project_export") - */ + #[Route(path: '/{id}/export', name: 'project_export')] public function exportEntity(Project $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); diff --git a/src/Controller/AdminPages/StorelocationController.php b/src/Controller/AdminPages/StorelocationController.php index 080690ab..7a9850ab 100644 --- a/src/Controller/AdminPages/StorelocationController.php +++ b/src/Controller/AdminPages/StorelocationController.php @@ -36,8 +36,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; /** - * @Route("/store_location") + * @see \App\Tests\Controller\AdminPages\StorelocationControllerTest */ +#[Route(path: '/store_location')] class StorelocationController extends BaseAdminController { protected string $entity_class = Storelocation::class; @@ -47,44 +48,34 @@ class StorelocationController extends BaseAdminController protected string $attachment_class = StorelocationAttachment::class; protected ?string $parameter_class = StorelocationParameter::class; - /** - * @Route("/{id}", name="store_location_delete", methods={"DELETE"}) - */ + #[Route(path: '/{id}', name: 'store_location_delete', methods: ['DELETE'])] public function delete(Request $request, Storelocation $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { return $this->_delete($request, $entity, $recursionHelper); } - /** - * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="store_location_edit") - * @Route("/{id}", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'store_location_edit')] + #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(Storelocation $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { return $this->_edit($entity, $request, $em, $timestamp); } - /** - * @Route("/new", name="store_location_new") - * @Route("/{id}/clone", name="store_location_clone") - * @Route("/") - */ + #[Route(path: '/new', name: 'store_location_new')] + #[Route(path: '/{id}/clone', name: 'store_location_clone')] + #[Route(path: '/')] public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Storelocation $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } - /** - * @Route("/export", name="store_location_export_all") - */ + #[Route(path: '/export', name: 'store_location_export_all')] public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response { return $this->_exportAll($em, $exporter, $request); } - /** - * @Route("/{id}/export", name="store_location_export") - */ + #[Route(path: '/{id}/export', name: 'store_location_export')] public function exportEntity(Storelocation $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); diff --git a/src/Controller/AdminPages/SupplierController.php b/src/Controller/AdminPages/SupplierController.php index f7505c6a..4cd5e46b 100644 --- a/src/Controller/AdminPages/SupplierController.php +++ b/src/Controller/AdminPages/SupplierController.php @@ -36,8 +36,9 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; /** - * @Route("/supplier") + * @see \App\Tests\Controller\AdminPages\SupplierControllerTest */ +#[Route(path: '/supplier')] class SupplierController extends BaseAdminController { protected string $entity_class = Supplier::class; @@ -47,44 +48,34 @@ class SupplierController extends BaseAdminController protected string $attachment_class = SupplierAttachment::class; protected ?string $parameter_class = SupplierParameter::class; - /** - * @Route("/{id}", name="supplier_delete", methods={"DELETE"}) - */ + #[Route(path: '/{id}', name: 'supplier_delete', methods: ['DELETE'])] public function delete(Request $request, Supplier $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { return $this->_delete($request, $entity, $recursionHelper); } - /** - * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="supplier_edit") - * @Route("/{id}", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'supplier_edit')] + #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function edit(Supplier $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response { return $this->_edit($entity, $request, $em, $timestamp); } - /** - * @Route("/new", name="supplier_new") - * @Route("/{id}/clone", name="supplier_clone") - * @Route("/") - */ + #[Route(path: '/new', name: 'supplier_new')] + #[Route(path: '/{id}/clone', name: 'supplier_clone')] + #[Route(path: '/')] public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Supplier $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } - /** - * @Route("/export", name="supplier_export_all") - */ + #[Route(path: '/export', name: 'supplier_export_all')] public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response { return $this->_exportAll($em, $exporter, $request); } - /** - * @Route("/{id}/export", name="supplier_export") - */ + #[Route(path: '/{id}/export', name: 'supplier_export')] public function exportEntity(Supplier $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); diff --git a/src/Controller/AttachmentFileController.php b/src/Controller/AttachmentFileController.php index 8df2a8bc..16b07ab9 100644 --- a/src/Controller/AttachmentFileController.php +++ b/src/Controller/AttachmentFileController.php @@ -42,9 +42,8 @@ class AttachmentFileController extends AbstractController { /** * Download the selected attachment. - * - * @Route("/attachment/{id}/download", name="attachment_download") */ + #[Route(path: '/attachment/{id}/download', name: 'attachment_download')] public function download(Attachment $attachment, AttachmentManager $helper): BinaryFileResponse { $this->denyAccessUnlessGranted('read', $attachment); @@ -72,9 +71,8 @@ class AttachmentFileController extends AbstractController /** * View the attachment. - * - * @Route("/attachment/{id}/view", name="attachment_view") */ + #[Route(path: '/attachment/{id}/view', name: 'attachment_view')] public function view(Attachment $attachment, AttachmentManager $helper): BinaryFileResponse { $this->denyAccessUnlessGranted('read', $attachment); @@ -100,9 +98,7 @@ class AttachmentFileController extends AbstractController return $response; } - /** - * @Route("/attachment/list", name="attachment_list") - */ + #[Route(path: '/attachment/list', name: 'attachment_list')] public function attachmentsTable(Request $request, DataTableFactory $dataTableFactory, NodesListBuilder $nodesListBuilder): Response { $this->denyAccessUnlessGranted('@attachments.list_attachments'); @@ -124,7 +120,7 @@ class AttachmentFileController extends AbstractController return $this->render('attachment_list.html.twig', [ 'datatable' => $table, - 'filterForm' => $filterForm->createView(), + 'filterForm' => $filterForm, ]); } } diff --git a/src/Controller/GroupController.php b/src/Controller/GroupController.php index 5aa8a371..718f0eba 100644 --- a/src/Controller/GroupController.php +++ b/src/Controller/GroupController.php @@ -39,9 +39,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; -/** - * @Route("/group") - */ +#[Route(path: '/group')] class GroupController extends BaseAdminController { protected string $entity_class = Group::class; @@ -51,10 +49,8 @@ class GroupController extends BaseAdminController protected string $attachment_class = GroupAttachment::class; protected ?string $parameter_class = GroupParameter::class; - /** - * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="group_edit") - * @Route("/{id}/", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'group_edit')] + #[Route(path: '/{id}/', requirements: ['id' => '\d+'])] public function edit(Group $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, PermissionSchemaUpdater $permissionSchemaUpdater, ?string $timestamp = null): Response { //Do an upgrade of the permission schema if needed (so the user can see the permissions a user get on next request (even if it was not done yet) @@ -63,7 +59,7 @@ class GroupController extends BaseAdminController //Handle permissions presets if ($request->request->has('permission_preset')) { $this->denyAccessUnlessGranted('edit_permissions', $entity); - if ($this->isCsrfTokenValid('group'.$entity->getId(), $request->request->get('_token'))) { + if ($this->isCsrfTokenValid('group'.$entity->getID(), $request->request->get('_token'))) { $preset = $request->request->get('permission_preset'); $permissionPresetsHelper->applyPreset($entity, $preset); @@ -82,35 +78,27 @@ class GroupController extends BaseAdminController return $this->_edit($entity, $request, $em, $timestamp); } - /** - * @Route("/new", name="group_new") - * @Route("/{id}/clone", name="group_clone") - * @Route("/") - */ + #[Route(path: '/new', name: 'group_new')] + #[Route(path: '/{id}/clone', name: 'group_clone')] + #[Route(path: '/')] public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?Group $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } - /** - * @Route("/{id}", name="group_delete", methods={"DELETE"}) - */ + #[Route(path: '/{id}', name: 'group_delete', methods: ['DELETE'])] public function delete(Request $request, Group $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { return $this->_delete($request, $entity, $recursionHelper); } - /** - * @Route("/export", name="group_export_all") - */ + #[Route(path: '/export', name: 'group_export_all')] public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response { return $this->_exportAll($em, $exporter, $request); } - /** - * @Route("/{id}/export", name="group_export") - */ + #[Route(path: '/{id}/export', name: 'group_export')] public function exportEntity(Group $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); diff --git a/src/Controller/HomepageController.php b/src/Controller/HomepageController.php index 51447c17..212363ff 100644 --- a/src/Controller/HomepageController.php +++ b/src/Controller/HomepageController.php @@ -37,33 +37,31 @@ use Symfony\Contracts\Cache\CacheInterface; class HomepageController extends AbstractController { - protected CacheInterface $cache; - protected KernelInterface $kernel; - protected DataTableFactory $dataTable; - - public function __construct(CacheInterface $cache, KernelInterface $kernel, DataTableFactory $dataTable) + public function __construct(protected CacheInterface $cache, protected KernelInterface $kernel, protected DataTableFactory $dataTable) { - $this->cache = $cache; - $this->kernel = $kernel; - $this->dataTable = $dataTable; } public function getBanner(): string { $banner = $this->getParameter('partdb.banner'); + if (!is_string($banner)) { + throw new \RuntimeException('The parameter "partdb.banner" must be a string.'); + } if (empty($banner)) { $banner_path = $this->kernel->getProjectDir() .DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'banner.md'; - return file_get_contents($banner_path); + $tmp = file_get_contents($banner_path); + if (false === $tmp) { + throw new \RuntimeException('The banner file could not be read.'); + } + $banner = $tmp; } return $banner; } - /** - * @Route("/", name="homepage") - */ + #[Route(path: '/', name: 'homepage')] public function homepage(Request $request, GitVersionInfo $versionInfo, EntityManagerInterface $entityManager): Response { if ($this->isGranted('@tools.lastActivity')) { diff --git a/src/Controller/LabelController.php b/src/Controller/LabelController.php index 769639d4..7d98019d 100644 --- a/src/Controller/LabelController.php +++ b/src/Controller/LabelController.php @@ -43,7 +43,9 @@ namespace App\Controller; use App\Entity\Base\AbstractDBElement; use App\Entity\LabelSystem\LabelOptions; +use App\Entity\LabelSystem\LabelProcessMode; use App\Entity\LabelSystem\LabelProfile; +use App\Entity\LabelSystem\LabelSupportedElement; use App\Exceptions\TwigModeException; use App\Form\LabelSystem\LabelDialogType; use App\Repository\DBElementRepository; @@ -59,59 +61,39 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Contracts\Translation\TranslatorInterface; -/** - * @Route("/label") - */ +#[Route(path: '/label')] class LabelController extends AbstractController { - protected LabelGenerator $labelGenerator; - protected EntityManagerInterface $em; - protected ElementTypeNameGenerator $elementTypeNameGenerator; - protected RangeParser $rangeParser; - protected TranslatorInterface $translator; - - public function __construct(LabelGenerator $labelGenerator, EntityManagerInterface $em, ElementTypeNameGenerator $elementTypeNameGenerator, - RangeParser $rangeParser, TranslatorInterface $translator) + public function __construct(protected LabelGenerator $labelGenerator, protected EntityManagerInterface $em, protected ElementTypeNameGenerator $elementTypeNameGenerator, protected RangeParser $rangeParser, protected TranslatorInterface $translator) { - $this->labelGenerator = $labelGenerator; - $this->em = $em; - $this->elementTypeNameGenerator = $elementTypeNameGenerator; - $this->rangeParser = $rangeParser; - $this->translator = $translator; } - /** - * @Route("/dialog", name="label_dialog") - * @Route("/{profile}/dialog", name="label_dialog_profile") - */ + #[Route(path: '/dialog', name: 'label_dialog')] + #[Route(path: '/{profile}/dialog', name: 'label_dialog_profile')] public function generator(Request $request, ?LabelProfile $profile = null): Response { $this->denyAccessUnlessGranted('@labels.create_labels'); //If we inherit a LabelProfile, the user need to have access to it... - if (null !== $profile) { + if ($profile instanceof LabelProfile) { $this->denyAccessUnlessGranted('read', $profile); } - if ($profile) { - $label_options = $profile->getOptions(); - } else { - $label_options = new LabelOptions(); - } + $label_options = $profile instanceof LabelProfile ? $profile->getOptions() : new LabelOptions(); //We have to disable the options, if twig mode is selected and user is not allowed to use it. - $disable_options = 'twig' === $label_options->getLinesMode() && !$this->isGranted('@labels.use_twig'); + $disable_options = (LabelProcessMode::TWIG === $label_options->getProcessMode()) && !$this->isGranted('@labels.use_twig'); $form = $this->createForm(LabelDialogType::class, null, [ 'disable_options' => $disable_options, ]); //Try to parse given target_type and target_id - $target_type = $request->query->get('target_type', null); + $target_type = $request->query->getEnum('target_type', LabelSupportedElement::class, null); $target_id = $request->query->get('target_id', null); $generate = $request->query->getBoolean('generate', false); - if (null === $profile && is_string($target_type)) { + if (!$profile instanceof LabelProfile && $target_type instanceof LabelSupportedElement) { $label_options->setSupportedElement($target_type); } if (is_string($target_id)) { @@ -128,10 +110,10 @@ class LabelController extends AbstractController $filename = 'invalid.pdf'; //Generate PDF either when the form is submitted and valid, or the form was not submit yet, and generate is set - if (($form->isSubmitted() && $form->isValid()) || ($generate && !$form->isSubmitted() && null !== $profile)) { + if (($form->isSubmitted() && $form->isValid()) || ($generate && !$form->isSubmitted() && $profile instanceof LabelProfile)) { $target_id = (string) $form->get('target_id')->getData(); $targets = $this->findObjects($form_options->getSupportedElement(), $target_id); - if (!empty($targets)) { + if ($targets !== []) { try { $pdf_data = $this->labelGenerator->generateLabel($form_options, $targets); $filename = $this->getLabelName($targets[0], $profile); @@ -146,7 +128,7 @@ class LabelController extends AbstractController } } - return $this->renderForm('label_system/dialog.html.twig', [ + return $this->render('label_system/dialog.html.twig', [ 'form' => $form, 'pdf_data' => $pdf_data, 'filename' => $filename, @@ -162,16 +144,12 @@ class LabelController extends AbstractController return $ret.'.pdf'; } - protected function findObjects(string $type, string $ids): array + protected function findObjects(LabelSupportedElement $type, string $ids): array { - if (!isset(LabelGenerator::CLASS_SUPPORT_MAPPING[$type])) { - throw new InvalidArgumentException('The given type is not known and can not be mapped to a class!'); - } - $id_array = $this->rangeParser->parse($ids); /** @var DBElementRepository $repo */ - $repo = $this->em->getRepository(LabelGenerator::CLASS_SUPPORT_MAPPING[$type]); + $repo = $this->em->getRepository($type->getEntityClass()); return $repo->getElementsFromIDArray($id_array); } diff --git a/src/Controller/LogController.php b/src/Controller/LogController.php index 24be85c9..afa32f7a 100644 --- a/src/Controller/LogController.php +++ b/src/Controller/LogController.php @@ -34,6 +34,7 @@ use App\Entity\LogSystem\ElementEditedLogEntry; use App\Form\Filters\LogFilterType; use App\Repository\DBElementRepository; use App\Services\LogSystem\EventUndoHelper; +use App\Services\LogSystem\EventUndoMode; use App\Services\LogSystem\LogEntryExtraFormatter; use App\Services\LogSystem\LogLevelHelper; use App\Services\LogSystem\LogTargetHelper; @@ -49,27 +50,17 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; -/** - * @Route("/log") - */ +#[Route(path: '/log')] class LogController extends AbstractController { - protected EntityManagerInterface $entityManager; - protected TimeTravel $timeTravel; protected DBElementRepository $dbRepository; - public function __construct(EntityManagerInterface $entityManager, TimeTravel $timeTravel) + public function __construct(protected EntityManagerInterface $entityManager, protected TimeTravel $timeTravel) { - $this->entityManager = $entityManager; - $this->timeTravel = $timeTravel; $this->dbRepository = $entityManager->getRepository(AbstractDBElement::class); } - /** - * @Route("/", name="log_view") - * - * @return Response - */ + #[Route(path: '/', name: 'log_view')] public function showLogs(Request $request, DataTableFactory $dataTable): Response { $this->denyAccessUnlessGranted('@system.show_logs'); @@ -93,17 +84,12 @@ class LogController extends AbstractController return $this->render('log_system/log_list.html.twig', [ 'datatable' => $table, - 'filterForm' => $filterForm->createView(), + 'filterForm' => $filterForm, ]); } - /** - * @Route("/{id}/details", name="log_details") - * @param Request $request - * @param AbstractLogEntry $logEntry - * @return Response - */ - public function logDetails(Request $request, AbstractLogEntry $logEntry, LogEntryExtraFormatter $logEntryExtraFormatter, + #[Route(path: '/{id}/details', name: 'log_details')] + public function logDetails(AbstractLogEntry $logEntry, LogEntryExtraFormatter $logEntryExtraFormatter, LogLevelHelper $logLevelHelper, LogTargetHelper $logTargetHelper, EntityManagerInterface $entityManager): Response { $this->denyAccessUnlessGranted('show_details', $logEntry); @@ -123,14 +109,12 @@ class LogController extends AbstractController ]); } - /** - * @Route("/{id}/delete", name="log_delete", methods={"DELETE"}) - */ + #[Route(path: '/{id}/delete', name: 'log_delete', methods: ['DELETE'])] public function deleteLogEntry(Request $request, AbstractLogEntry $logEntry, EntityManagerInterface $entityManager): RedirectResponse { $this->denyAccessUnlessGranted('delete', $logEntry); - if ($this->isCsrfTokenValid('delete'.$logEntry->getId(), $request->request->get('_token'))) { + if ($this->isCsrfTokenValid('delete'.$logEntry->getID(), $request->request->get('_token'))) { //Remove part $entityManager->remove($logEntry); //Flush changes @@ -142,22 +126,24 @@ class LogController extends AbstractController } - /** - * @Route("/undo", name="log_undo", methods={"POST"}) - */ + #[Route(path: '/undo', name: 'log_undo', methods: ['POST'])] public function undoRevertLog(Request $request, EventUndoHelper $eventUndoHelper): RedirectResponse { - $mode = EventUndoHelper::MODE_UNDO; - $id = $request->request->get('undo'); + $mode = EventUndoMode::UNDO; + $id = $request->request->getInt('undo'); //If no undo value was set check if a revert was set - if (null === $id) { - $id = $request->get('revert'); - $mode = EventUndoHelper::MODE_REVERT; + if (0 === $id) { + $id = $request->request->getInt('revert'); + $mode = EventUndoMode::REVERT; + } + + if (0 === $id) { + throw new InvalidArgumentException('No log entry ID was given!'); } $log_element = $this->entityManager->find(AbstractLogEntry::class, $id); - if (null === $log_element) { + if (!$log_element instanceof AbstractLogEntry) { throw new InvalidArgumentException('No log entry with the given ID is existing!'); } @@ -166,9 +152,9 @@ class LogController extends AbstractController $eventUndoHelper->setMode($mode); $eventUndoHelper->setUndoneEvent($log_element); - if (EventUndoHelper::MODE_UNDO === $mode) { + if (EventUndoMode::UNDO === $mode) { $this->undoLog($log_element); - } elseif (EventUndoHelper::MODE_REVERT === $mode) { + } elseif (EventUndoMode::REVERT === $mode) { $this->revertLog($log_element); } diff --git a/src/Controller/PartController.php b/src/Controller/PartController.php index b7a3fd0e..62dbaf4e 100644 --- a/src/Controller/PartController.php +++ b/src/Controller/PartController.php @@ -48,6 +48,7 @@ use Doctrine\ORM\EntityManagerInterface; use Exception; use Omines\DataTablesBundle\DataTableFactory; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; +use Symfony\Bridge\Doctrine\Attribute\MapEntity; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -59,29 +60,19 @@ use Symfony\Contracts\Translation\TranslatorInterface; use function Symfony\Component\Translation\t; -/** - * @Route("/part") - */ +#[Route(path: '/part')] class PartController extends AbstractController { - protected PricedetailHelper $pricedetailHelper; - protected PartPreviewGenerator $partPreviewGenerator; - protected EventCommentHelper $commentHelper; - - public function __construct(PricedetailHelper $pricedetailHelper, - PartPreviewGenerator $partPreviewGenerator, EventCommentHelper $commentHelper) + public function __construct(protected PricedetailHelper $pricedetailHelper, protected PartPreviewGenerator $partPreviewGenerator, protected EventCommentHelper $commentHelper) { - $this->pricedetailHelper = $pricedetailHelper; - $this->partPreviewGenerator = $partPreviewGenerator; - $this->commentHelper = $commentHelper; } /** - * @Route("/{id}/info/{timestamp}", name="part_info") - * @Route("/{id}", requirements={"id"="\d+"}) * * @throws Exception */ + #[Route(path: '/{id}/info/{timestamp}', name: 'part_info')] + #[Route(path: '/{id}', requirements: ['id' => '\d+'])] public function show(Part $part, Request $request, TimeTravel $timeTravel, HistoryHelper $historyHelper, DataTableFactory $dataTable, ParameterExtractor $parameterExtractor, PartLotWithdrawAddHelper $withdrawAddHelper, ?string $timestamp = null): Response { @@ -129,9 +120,7 @@ class PartController extends AbstractController ); } - /** - * @Route("/{id}/edit", name="part_edit") - */ + #[Route(path: '/{id}/edit', name: 'part_edit')] public function edit(Part $part, Request $request, EntityManagerInterface $em, TranslatorInterface $translator, AttachmentSubmitHandler $attachmentSubmitHandler): Response { @@ -182,21 +171,19 @@ class PartController extends AbstractController $this->addFlash('error', 'part.edited_flash.invalid'); } - return $this->renderForm('parts/edit/edit_part_info.html.twig', + return $this->render('parts/edit/edit_part_info.html.twig', [ 'part' => $part, 'form' => $form, ]); } - /** - * @Route("/{id}/delete", name="part_delete", methods={"DELETE"}) - */ + #[Route(path: '/{id}/delete', name: 'part_delete', methods: ['DELETE'])] public function delete(Request $request, Part $part, EntityManagerInterface $entityManager): RedirectResponse { $this->denyAccessUnlessGranted('delete', $part); - if ($this->isCsrfTokenValid('delete'.$part->getId(), $request->request->get('_token'))) { + if ($this->isCsrfTokenValid('delete'.$part->getID(), $request->request->get('_token'))) { $this->commentHelper->setMessage($request->request->get('log_comment', null)); @@ -212,23 +199,22 @@ class PartController extends AbstractController return $this->redirectToRoute('homepage'); } - /** - * @Route("/new", name="part_new") - * @Route("/{id}/clone", name="part_clone") - * @Route("/new_build_part/{project_id}", name="part_new_build_part") - * @ParamConverter("part", options={"id" = "id"}) - * @ParamConverter("project", options={"id" = "project_id"}) - */ + #[Route(path: '/new', name: 'part_new')] + #[Route(path: '/{id}/clone', name: 'part_clone')] + #[Route(path: '/new_build_part/{project_id}', name: 'part_new_build_part')] public function new(Request $request, EntityManagerInterface $em, TranslatorInterface $translator, AttachmentSubmitHandler $attachmentSubmitHandler, ProjectBuildPartHelper $projectBuildPartHelper, - ?Part $part = null, ?Project $project = null): Response + #[MapEntity(mapping: ['id' => 'id'])] ?Part $part = null, + #[MapEntity(mapping: ['id' => 'project_id'])] ?Project $project = null): Response { - if ($part) { //Clone part + if ($part instanceof Part) { + //Clone part $new_part = clone $part; - } else if ($project) { //Initialize a new part for a build part from the given project + } elseif ($project instanceof Project) { + //Initialize a new part for a build part from the given project //Ensure that the project has not already a build part - if ($project->getBuildPart() !== null) { + if ($project->getBuildPart() instanceof Part) { $this->addFlash('error', 'part.new_build_part.error.build_part_already_exists'); return $this->redirectToRoute('part_edit', ['id' => $project->getBuildPart()->getID()]); } @@ -241,7 +227,7 @@ class PartController extends AbstractController $cid = $request->get('category', null); $category = $cid ? $em->find(Category::class, $cid) : null; - if (null !== $category && null === $new_part->getCategory()) { + if ($category instanceof Category && !$new_part->getCategory() instanceof Category) { $new_part->setCategory($category); $new_part->setDescription($category->getDefaultDescription()); $new_part->setComment($category->getDefaultComment()); @@ -249,19 +235,19 @@ class PartController extends AbstractController $fid = $request->get('footprint', null); $footprint = $fid ? $em->find(Footprint::class, $fid) : null; - if (null !== $footprint && null === $new_part->getFootprint()) { + if ($footprint instanceof Footprint && !$new_part->getFootprint() instanceof Footprint) { $new_part->setFootprint($footprint); } $mid = $request->get('manufacturer', null); $manufacturer = $mid ? $em->find(Manufacturer::class, $mid) : null; - if (null !== $manufacturer && null === $new_part->getManufacturer()) { + if ($manufacturer instanceof Manufacturer && !$new_part->getManufacturer() instanceof Manufacturer) { $new_part->setManufacturer($manufacturer); } $store_id = $request->get('storelocation', null); $storelocation = $store_id ? $em->find(Storelocation::class, $store_id) : null; - if (null !== $storelocation && $new_part->getPartLots()->isEmpty()) { + if ($storelocation instanceof Storelocation && $new_part->getPartLots()->isEmpty()) { $partLot = new PartLot(); $partLot->setStorageLocation($storelocation); $partLot->setInstockUnknown(true); @@ -270,7 +256,7 @@ class PartController extends AbstractController $supplier_id = $request->get('supplier', null); $supplier = $supplier_id ? $em->find(Supplier::class, $supplier_id) : null; - if (null !== $supplier && $new_part->getOrderdetails()->isEmpty()) { + if ($supplier instanceof Supplier && $new_part->getOrderdetails()->isEmpty()) { $orderdetail = new Orderdetail(); $orderdetail->setSupplier($supplier); $new_part->addOrderdetail($orderdetail); @@ -328,22 +314,20 @@ class PartController extends AbstractController $this->addFlash('error', 'part.created_flash.invalid'); } - return $this->renderForm('parts/edit/new_part.html.twig', + return $this->render('parts/edit/new_part.html.twig', [ 'part' => $new_part, 'form' => $form, ]); } - /** - * @Route("/{id}/add_withdraw", name="part_add_withdraw", methods={"POST"}) - */ + #[Route(path: '/{id}/add_withdraw', name: 'part_add_withdraw', methods: ['POST'])] public function withdrawAddHandler(Part $part, Request $request, EntityManagerInterface $em, PartLotWithdrawAddHelper $withdrawAddHelper): Response { if ($this->isCsrfTokenValid('part_withraw' . $part->getID(), $request->request->get('_csfr'))) { //Retrieve partlot from the request $partLot = $em->find(PartLot::class, $request->request->get('lot_id')); - if($partLot === null) { + if(!$partLot instanceof PartLot) { throw new \RuntimeException('Part lot not found!'); } //Ensure that the partlot belongs to the part @@ -383,7 +367,7 @@ class PartController extends AbstractController default: throw new \RuntimeException("Unknown action!"); } - } catch (AccessDeniedException $exception) { + } catch (AccessDeniedException) { $this->addFlash('error', t('part.withdraw.access_denied')); goto err; } diff --git a/src/Controller/PartImportExportController.php b/src/Controller/PartImportExportController.php index 81754b3b..326ea923 100644 --- a/src/Controller/PartImportExportController.php +++ b/src/Controller/PartImportExportController.php @@ -1,4 +1,7 @@ . */ - namespace App\Controller; use App\Entity\Parts\Part; @@ -36,23 +38,11 @@ use UnexpectedValueException; class PartImportExportController extends AbstractController { - private PartsTableActionHandler $partsTableActionHandler; - private EntityImporter $entityImporter; - private EventCommentHelper $commentHelper; - - public function __construct(PartsTableActionHandler $partsTableActionHandler, - EntityImporter $entityImporter, EventCommentHelper $commentHelper) + public function __construct(private readonly PartsTableActionHandler $partsTableActionHandler, private readonly EntityImporter $entityImporter, private readonly EventCommentHelper $commentHelper) { - $this->partsTableActionHandler = $partsTableActionHandler; - $this->entityImporter = $entityImporter; - $this->commentHelper = $commentHelper; } - /** - * @Route("/parts/import", name="parts_import") - * @param Request $request - * @return Response - */ + #[Route(path: '/parts/import', name: 'parts_import')] public function importParts(Request $request): Response { $this->denyAccessUnlessGranted('@parts.import'); @@ -109,23 +99,20 @@ class PartImportExportController extends AbstractController ret: - return $this->renderForm('parts/import/parts_import.html.twig', [ + return $this->render('parts/import/parts_import.html.twig', [ 'import_form' => $import_form, 'imported_entities' => $entities ?? [], 'import_errors' => $errors ?? [], ]); } - /** - * @Route("/parts/export", name="parts_export", methods={"GET"}) - * @return Response - */ + #[Route(path: '/parts/export', name: 'parts_export', methods: ['GET'])] public function exportParts(Request $request, EntityExporter $entityExporter): Response { $ids = $request->query->get('ids', ''); $parts = $this->partsTableActionHandler->idStringToArray($ids); - if (empty($parts)) { + if ($parts === []) { throw new \RuntimeException('No parts found!'); } @@ -136,4 +123,4 @@ class PartImportExportController extends AbstractController return $entityExporter->exportEntityFromRequest($parts, $request); } -} \ No newline at end of file +} diff --git a/src/Controller/PartListsController.php b/src/Controller/PartListsController.php index 52d65d2a..90005447 100644 --- a/src/Controller/PartListsController.php +++ b/src/Controller/PartListsController.php @@ -48,23 +48,11 @@ use Symfony\Contracts\Translation\TranslatorInterface; class PartListsController extends AbstractController { - private EntityManagerInterface $entityManager; - private NodesListBuilder $nodesListBuilder; - private DataTableFactory $dataTableFactory; - - private TranslatorInterface $translator; - - public function __construct(EntityManagerInterface $entityManager, NodesListBuilder $nodesListBuilder, DataTableFactory $dataTableFactory, TranslatorInterface $translator) + public function __construct(private readonly EntityManagerInterface $entityManager, private readonly NodesListBuilder $nodesListBuilder, private readonly DataTableFactory $dataTableFactory, private readonly TranslatorInterface $translator) { - $this->entityManager = $entityManager; - $this->nodesListBuilder = $nodesListBuilder; - $this->dataTableFactory = $dataTableFactory; - $this->translator = $translator; } - /** - * @Route("/table/action", name="table_action", methods={"POST"}) - */ + #[Route(path: '/table/action', name: 'table_action', methods: ['POST'])] public function tableAction(Request $request, PartsTableActionHandler $actionHandler): Response { $this->denyAccessUnlessGranted('@parts.edit'); @@ -102,8 +90,6 @@ class PartListsController extends AbstractController /** * Disable the given form interface after creation of the form by removing and reattaching the form. - * @param FormInterface $form - * @return void */ private function disableFormFieldAfterCreation(FormInterface $form, bool $disabled = true): void { @@ -111,12 +97,12 @@ class PartListsController extends AbstractController $attrs['disabled'] = $disabled; $parent = $form->getParent(); - if ($parent === null) { + if (!$parent instanceof FormInterface) { throw new \RuntimeException('This function can only be used on form fields that are children of another form!'); } $parent->remove($form->getName()); - $parent->add($form->getName(), get_class($form->getConfig()->getType()->getInnerType()), $attrs); + $parent->add($form->getName(), $form->getConfig()->getType()->getInnerType()::class, $attrs); } /** @@ -127,7 +113,6 @@ class PartListsController extends AbstractController * @param callable|null $form_changer A function that is called with the form object as parameter. This function can be used to customize the form * @param array $additonal_template_vars Any additional template variables that should be passed to the template * @param array $additional_table_vars Any additional variables that should be passed to the table creation - * @return Response */ protected function showListWithFilter(Request $request, string $template, ?callable $filter_changer = null, ?callable $form_changer = null, array $additonal_template_vars = [], array $additional_table_vars = []): Response { @@ -174,11 +159,7 @@ class PartListsController extends AbstractController ], $additonal_template_vars)); } - /** - * @Route("/category/{id}/parts", name="part_list_category") - * - * @return JsonResponse|Response - */ + #[Route(path: '/category/{id}/parts', name: 'part_list_category')] public function showCategory(Category $category, Request $request): Response { $this->denyAccessUnlessGranted('@categories.read'); @@ -186,7 +167,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/category_list.html.twig', function (PartFilter $filter) use ($category) { - $filter->getCategory()->setOperator('INCLUDING_CHILDREN')->setValue($category); + $filter->category->setOperator('INCLUDING_CHILDREN')->setValue($category); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('category')->get('value')); }, [ @@ -196,11 +177,7 @@ class PartListsController extends AbstractController ); } - /** - * @Route("/footprint/{id}/parts", name="part_list_footprint") - * - * @return JsonResponse|Response - */ + #[Route(path: '/footprint/{id}/parts', name: 'part_list_footprint')] public function showFootprint(Footprint $footprint, Request $request): Response { $this->denyAccessUnlessGranted('@footprints.read'); @@ -208,7 +185,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/footprint_list.html.twig', function (PartFilter $filter) use ($footprint) { - $filter->getFootprint()->setOperator('INCLUDING_CHILDREN')->setValue($footprint); + $filter->footprint->setOperator('INCLUDING_CHILDREN')->setValue($footprint); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('footprint')->get('value')); }, [ @@ -218,11 +195,7 @@ class PartListsController extends AbstractController ); } - /** - * @Route("/manufacturer/{id}/parts", name="part_list_manufacturer") - * - * @return JsonResponse|Response - */ + #[Route(path: '/manufacturer/{id}/parts', name: 'part_list_manufacturer')] public function showManufacturer(Manufacturer $manufacturer, Request $request): Response { $this->denyAccessUnlessGranted('@manufacturers.read'); @@ -230,7 +203,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/manufacturer_list.html.twig', function (PartFilter $filter) use ($manufacturer) { - $filter->getManufacturer()->setOperator('INCLUDING_CHILDREN')->setValue($manufacturer); + $filter->manufacturer->setOperator('INCLUDING_CHILDREN')->setValue($manufacturer); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('manufacturer')->get('value')); }, [ @@ -240,11 +213,7 @@ class PartListsController extends AbstractController ); } - /** - * @Route("/store_location/{id}/parts", name="part_list_store_location") - * - * @return JsonResponse|Response - */ + #[Route(path: '/store_location/{id}/parts', name: 'part_list_store_location')] public function showStorelocation(Storelocation $storelocation, Request $request): Response { $this->denyAccessUnlessGranted('@storelocations.read'); @@ -252,7 +221,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/store_location_list.html.twig', function (PartFilter $filter) use ($storelocation) { - $filter->getStorelocation()->setOperator('INCLUDING_CHILDREN')->setValue($storelocation); + $filter->storelocation->setOperator('INCLUDING_CHILDREN')->setValue($storelocation); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('storelocation')->get('value')); }, [ @@ -262,11 +231,7 @@ class PartListsController extends AbstractController ); } - /** - * @Route("/supplier/{id}/parts", name="part_list_supplier") - * - * @return JsonResponse|Response - */ + #[Route(path: '/supplier/{id}/parts', name: 'part_list_supplier')] public function showSupplier(Supplier $supplier, Request $request): Response { $this->denyAccessUnlessGranted('@suppliers.read'); @@ -274,7 +239,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/supplier_list.html.twig', function (PartFilter $filter) use ($supplier) { - $filter->getSupplier()->setOperator('INCLUDING_CHILDREN')->setValue($supplier); + $filter->supplier->setOperator('INCLUDING_CHILDREN')->setValue($supplier); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('supplier')->get('value')); }, [ @@ -284,11 +249,7 @@ class PartListsController extends AbstractController ); } - /** - * @Route("/parts/by_tag/{tag}", name="part_list_tags", requirements={"tag": ".*"}) - * - * @return JsonResponse|Response - */ + #[Route(path: '/parts/by_tag/{tag}', name: 'part_list_tags', requirements: ['tag' => '.*'])] public function showTag(string $tag, Request $request): Response { $tag = trim($tag); @@ -296,7 +257,7 @@ class PartListsController extends AbstractController return $this->showListWithFilter($request, 'parts/lists/tags_list.html.twig', function (PartFilter $filter) use ($tag) { - $filter->getTags()->setOperator('ANY')->setValue($tag); + $filter->tags->setOperator('ANY')->setValue($tag); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('tags')->get('value')); }, [ @@ -329,11 +290,7 @@ class PartListsController extends AbstractController return $filter; } - /** - * @Route("/parts/search", name="parts_search") - * - * @return JsonResponse|Response - */ + #[Route(path: '/parts/search', name: 'parts_search')] public function showSearch(Request $request, DataTableFactory $dataTable): Response { $searchFilter = $this->searchRequestToFilter($request); @@ -352,11 +309,7 @@ class PartListsController extends AbstractController ); } - /** - * @Route("/parts", name="parts_show_all") - * - * @return Response - */ + #[Route(path: '/parts', name: 'parts_show_all')] public function showAll(Request $request): Response { return $this->showListWithFilter($request,'parts/lists/all_list.html.twig'); diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index 56ba6b7d..08ddc258 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -1,4 +1,7 @@ . */ - namespace App\Controller; use App\DataTables\ProjectBomEntriesDataTable; @@ -47,21 +49,14 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; use function Symfony\Component\Translation\t; -/** - * @Route("/project") - */ +#[Route(path: '/project')] class ProjectController extends AbstractController { - private DataTableFactory $dataTableFactory; - - public function __construct(DataTableFactory $dataTableFactory) + public function __construct(private readonly DataTableFactory $dataTableFactory) { - $this->dataTableFactory = $dataTableFactory; } - /** - * @Route("/{id}/info", name="project_info", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/info', name: 'project_info', requirements: ['id' => '\d+'])] public function info(Project $project, Request $request, ProjectBuildHelper $buildHelper): Response { $this->denyAccessUnlessGranted('read', $project); @@ -80,9 +75,7 @@ class ProjectController extends AbstractController ]); } - /** - * @Route("/{id}/build", name="project_build", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/build', name: 'project_build', requirements: ['id' => '\d+'])] public function build(Project $project, Request $request, ProjectBuildHelper $buildHelper, EntityManagerInterface $entityManager): Response { $this->denyAccessUnlessGranted('read', $project); @@ -117,7 +110,7 @@ class ProjectController extends AbstractController $this->addFlash('error', 'project.build.flash.invalid_input'); } - return $this->renderForm('projects/build/build.html.twig', [ + return $this->render('projects/build/build.html.twig', [ 'buildHelper' => $buildHelper, 'project' => $project, 'build_request' => $projectBuildRequest, @@ -126,9 +119,7 @@ class ProjectController extends AbstractController ]); } - /** - * @Route("/{id}/import_bom", name="project_import_bom", requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}/import_bom', name: 'project_import_bom', requirements: ['id' => '\d+'])] public function importBOM(Request $request, EntityManagerInterface $entityManager, Project $project, BOMImporter $BOMImporter, ValidatorInterface $validator): Response { @@ -185,32 +176,26 @@ class ProjectController extends AbstractController return $this->redirectToRoute('project_edit', ['id' => $project->getID()]); } - if (count ($errors) > 0) { - $this->addFlash('error', t('project.bom_import.flash.invalid_entries')); - } - } catch (\UnexpectedValueException $e) { - $this->addFlash('error', t('project.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()])); - } catch (SyntaxError $e) { + //When we get here, there were validation errors + $this->addFlash('error', t('project.bom_import.flash.invalid_entries')); + + } catch (\UnexpectedValueException|SyntaxError $e) { $this->addFlash('error', t('project.bom_import.flash.invalid_file', ['%message%' => $e->getMessage()])); } } - return $this->renderForm('projects/import_bom.html.twig', [ + return $this->render('projects/import_bom.html.twig', [ 'project' => $project, 'form' => $form, 'errors' => $errors ?? null, ]); } - /** - * @Route("/add_parts", name="project_add_parts_no_id") - * @Route("/{id}/add_parts", name="project_add_parts", requirements={"id"="\d+"}) - * @param Request $request - * @param Project|null $project - */ + #[Route(path: '/add_parts', name: 'project_add_parts_no_id')] + #[Route(path: '/{id}/add_parts', name: 'project_add_parts', requirements: ['id' => '\d+'])] public function addPart(Request $request, EntityManagerInterface $entityManager, ?Project $project): Response { - if($project) { + if($project instanceof Project) { $this->denyAccessUnlessGranted('edit', $project); } else { $this->denyAccessUnlessGranted('@projects.edit'); @@ -220,7 +205,7 @@ class ProjectController extends AbstractController $builder->add('project', StructuralEntityType::class, [ 'class' => Project::class, 'required' => true, - 'disabled' => $project !== null, //If a project is given, disable the field + 'disabled' => $project instanceof Project, //If a project is given, disable the field 'data' => $project, 'constraints' => [ new NotNull() @@ -232,7 +217,7 @@ class ProjectController extends AbstractController //Preset the BOM entries with the selected parts, when the form was not submitted yet $preset_data = new ArrayCollection(); - foreach (explode(',', $request->get('parts', '')) as $part_id) { + foreach (explode(',', (string) $request->get('parts', '')) as $part_id) { $part = $entityManager->getRepository(Part::class)->find($part_id); if (null !== $part) { //If there is already a BOM entry for this part, we use this one (we edit it then) @@ -274,9 +259,9 @@ class ProjectController extends AbstractController return $this->redirectToRoute('project_info', ['id' => $target_project->getID()]); } - return $this->renderForm('projects/add_parts.html.twig', [ + return $this->render('projects/add_parts.html.twig', [ 'project' => $project, 'form' => $form, ]); } -} \ No newline at end of file +} diff --git a/src/Controller/RedirectController.php b/src/Controller/RedirectController.php index 811ab135..b0af0119 100644 --- a/src/Controller/RedirectController.php +++ b/src/Controller/RedirectController.php @@ -30,17 +30,13 @@ use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Contracts\Translation\TranslatorInterface; +/** + * @see \App\Tests\Controller\RedirectControllerTest + */ class RedirectController extends AbstractController { - protected string $default_locale; - protected TranslatorInterface $translator; - protected bool $enforce_index_php; - - public function __construct(string $default_locale, TranslatorInterface $translator, bool $enforce_index_php) + public function __construct(protected string $default_locale, protected TranslatorInterface $translator, protected bool $enforce_index_php) { - $this->default_locale = $default_locale; - $this->translator = $translator; - $this->enforce_index_php = $enforce_index_php; } /** @@ -54,7 +50,7 @@ class RedirectController extends AbstractController //Check if a user has set a preferred language setting: $user = $this->getUser(); - if (($user instanceof User) && !empty($user->getLanguage())) { + if (($user instanceof User) && ($user->getLanguage() !== null && $user->getLanguage() !== '')) { $locale = $user->getLanguage(); } @@ -62,7 +58,7 @@ class RedirectController extends AbstractController //If either mod_rewrite is not enabled or the index.php version is enforced, add index.php to the string if (($this->enforce_index_php || !$this->checkIfModRewriteAvailable()) - && false === strpos($new_url, 'index.php')) { + && !str_contains((string) $new_url, 'index.php')) { //Like Request::getUriForPath only with index.php $new_url = $request->getSchemeAndHttpHost().$request->getBaseUrl().'/index.php/'.$locale.$request->getPathInfo(); } @@ -87,6 +83,6 @@ class RedirectController extends AbstractController } //Check if the mod_rewrite module is loaded - return in_array('mod_rewrite', apache_get_modules(), false); + return in_array('mod_rewrite', apache_get_modules(), true); } } diff --git a/src/Controller/ScanController.php b/src/Controller/ScanController.php index 8c0c9ad8..41959d0a 100644 --- a/src/Controller/ScanController.php +++ b/src/Controller/ScanController.php @@ -51,23 +51,14 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; -/** - * @Route("/scan") - */ +#[Route(path: '/scan')] class ScanController extends AbstractController { - protected BarcodeRedirector $barcodeParser; - protected BarcodeNormalizer $barcodeNormalizer; - - public function __construct(BarcodeRedirector $barcodeParser, BarcodeNormalizer $barcodeNormalizer) + public function __construct(protected BarcodeRedirector $barcodeParser, protected BarcodeNormalizer $barcodeNormalizer) { - $this->barcodeParser = $barcodeParser; - $this->barcodeNormalizer = $barcodeNormalizer; } - /** - * @Route("", name="scan_dialog") - */ + #[Route(path: '', name: 'scan_dialog')] public function dialog(Request $request): Response { $this->denyAccessUnlessGranted('@tools.label_scanner'); @@ -83,15 +74,15 @@ class ScanController extends AbstractController try { return $this->redirect($this->barcodeParser->getRedirectURL($type, $id)); - } catch (EntityNotFoundException $exception) { + } catch (EntityNotFoundException) { $this->addFlash('success', 'scan.qr_not_found'); } - } catch (InvalidArgumentException $exception) { + } catch (InvalidArgumentException) { $this->addFlash('error', 'scan.format_unknown'); } } - return $this->renderForm('label_system/scanner/scanner.html.twig', [ + return $this->render('label_system/scanner/scanner.html.twig', [ 'form' => $form, ]); } @@ -105,7 +96,7 @@ class ScanController extends AbstractController $this->addFlash('success', 'scan.qr_success'); return $this->redirect($this->barcodeParser->getRedirectURL($type, $id)); - } catch (EntityNotFoundException $exception) { + } catch (EntityNotFoundException) { $this->addFlash('success', 'scan.qr_not_found'); return $this->redirectToRoute('homepage'); diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php index 026e18c1..480d5f58 100644 --- a/src/Controller/SecurityController.php +++ b/src/Controller/SecurityController.php @@ -48,18 +48,11 @@ use Symfony\Contracts\Translation\TranslatorInterface; class SecurityController extends AbstractController { - protected TranslatorInterface $translator; - protected bool $allow_email_pw_reset; - - public function __construct(TranslatorInterface $translator, bool $allow_email_pw_reset) + public function __construct(protected TranslatorInterface $translator, protected bool $allow_email_pw_reset) { - $this->translator = $translator; - $this->allow_email_pw_reset = $allow_email_pw_reset; } - /** - * @Route("/login", name="login", methods={"GET", "POST"}) - */ + #[Route(path: '/login', name: 'login', methods: ['GET', 'POST'])] public function login(AuthenticationUtils $authenticationUtils): Response { // get the login error if there is one @@ -75,11 +68,10 @@ class SecurityController extends AbstractController } /** - * @Route("/pw_reset/request", name="pw_reset_request") - * * @return RedirectResponse|Response */ - public function requestPwReset(PasswordResetManager $passwordReset, Request $request) + #[Route(path: '/pw_reset/request', name: 'pw_reset_request')] + public function requestPwReset(PasswordResetManager $passwordReset, Request $request): RedirectResponse|Response { if (!$this->allow_email_pw_reset) { throw new AccessDeniedHttpException('The password reset via email is disabled!'); @@ -113,17 +105,16 @@ class SecurityController extends AbstractController return $this->redirectToRoute('login'); } - return $this->renderForm('security/pw_reset_request.html.twig', [ + return $this->render('security/pw_reset_request.html.twig', [ 'form' => $form, ]); } /** - * @Route("/pw_reset/new_pw/{user}/{token}", name="pw_reset_new_pw") - * * @return RedirectResponse|Response */ - public function pwResetNewPw(PasswordResetManager $passwordReset, Request $request, EntityManagerInterface $em, EventDispatcherInterface $eventDispatcher, ?string $user = null, ?string $token = null) + #[Route(path: '/pw_reset/new_pw/{user}/{token}', name: 'pw_reset_new_pw')] + public function pwResetNewPw(PasswordResetManager $passwordReset, Request $request, EntityManagerInterface $em, EventDispatcherInterface $eventDispatcher, ?string $user = null, ?string $token = null): RedirectResponse|Response { if (!$this->allow_email_pw_reset) { throw new AccessDeniedHttpException('The password reset via email is disabled!'); @@ -187,15 +178,13 @@ class SecurityController extends AbstractController } } - return $this->renderForm('security/pw_reset_new_pw.html.twig', [ + return $this->render('security/pw_reset_new_pw.html.twig', [ 'form' => $form, ]); } - /** - * @Route("/logout", name="logout") - */ - public function logout(): void + #[Route(path: '/logout', name: 'logout')] + public function logout(): never { throw new RuntimeException('Will be intercepted before getting here'); } diff --git a/src/Controller/SelectAPIController.php b/src/Controller/SelectAPIController.php index f01f03ca..c403ab82 100644 --- a/src/Controller/SelectAPIController.php +++ b/src/Controller/SelectAPIController.php @@ -1,4 +1,7 @@ . */ - namespace App\Controller; use App\Entity\Base\AbstractNamedDBElement; @@ -37,66 +39,46 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Contracts\Translation\TranslatorInterface; /** - * @Route("/select_api") - * * This endpoint is used by the select2 library to dynamically load data (used in the multiselect action helper in parts lists) */ +#[Route(path: '/select_api')] class SelectAPIController extends AbstractController { - private NodesListBuilder $nodesListBuilder; - private TranslatorInterface $translator; - private StructuralEntityChoiceHelper $choiceHelper; - - public function __construct(NodesListBuilder $nodesListBuilder, TranslatorInterface $translator, StructuralEntityChoiceHelper $choiceHelper) + public function __construct(private readonly NodesListBuilder $nodesListBuilder, private readonly TranslatorInterface $translator, private readonly StructuralEntityChoiceHelper $choiceHelper) { - $this->nodesListBuilder = $nodesListBuilder; - $this->translator = $translator; - $this->choiceHelper = $choiceHelper; } - /** - * @Route("/category", name="select_category") - */ + #[Route(path: '/category', name: 'select_category')] public function category(): Response { return $this->getResponseForClass(Category::class); } - /** - * @Route("/footprint", name="select_footprint") - */ + #[Route(path: '/footprint', name: 'select_footprint')] public function footprint(): Response { return $this->getResponseForClass(Footprint::class, true); } - /** - * @Route("/manufacturer", name="select_manufacturer") - */ + #[Route(path: '/manufacturer', name: 'select_manufacturer')] public function manufacturer(): Response { return $this->getResponseForClass(Manufacturer::class, true); } - /** - * @Route("/measurement_unit", name="select_measurement_unit") - */ + #[Route(path: '/measurement_unit', name: 'select_measurement_unit')] public function measurement_unit(): Response { return $this->getResponseForClass(MeasurementUnit::class, true); } - /** - * @Route("/project", name="select_project") - */ + #[Route(path: '/project', name: 'select_project')] public function projects(): Response { return $this->getResponseForClass(Project::class, false); } - /** - * @Route("/export_level", name="select_export_level") - */ + #[Route(path: '/export_level', name: 'select_export_level')] public function exportLevel(): Response { $entries = [ @@ -105,18 +87,13 @@ class SelectAPIController extends AbstractController 3 => $this->translator->trans('export.level.full'), ]; - return $this->json(array_map(static function ($key, $value) { - return [ - 'text' => $value, - 'value' => $key, - ]; - }, array_keys($entries), $entries)); + return $this->json(array_map(static fn($key, $value) => [ + 'text' => $value, + 'value' => $key, + ], array_keys($entries), $entries)); } - /** - * @Route("/label_profiles", name="select_label_profiles") - * @return Response - */ + #[Route(path: '/label_profiles', name: 'select_label_profiles')] public function labelProfiles(EntityManagerInterface $entityManager): Response { $this->denyAccessUnlessGranted('@labels.create_labels'); @@ -134,10 +111,7 @@ class SelectAPIController extends AbstractController return $this->json($nodes); } - /** - * @Route("/label_profiles_lot", name="select_label_profiles_lot") - * @return Response - */ + #[Route(path: '/label_profiles_lot', name: 'select_label_profiles_lot')] public function labelProfilesLot(EntityManagerInterface $entityManager): Response { $this->denyAccessUnlessGranted('@labels.create_labels'); @@ -198,7 +172,7 @@ class SelectAPIController extends AbstractController //Remove the data-* prefix for each key $data = array_combine( array_map(static function ($key) { - if (strpos($key, 'data-') === 0) { + if (str_starts_with($key, 'data-')) { return substr($key, 5); } return $key; diff --git a/src/Controller/StatisticsController.php b/src/Controller/StatisticsController.php index d9c467f4..6ff09e83 100644 --- a/src/Controller/StatisticsController.php +++ b/src/Controller/StatisticsController.php @@ -48,9 +48,7 @@ use Symfony\Component\Routing\Annotation\Route; class StatisticsController extends AbstractController { - /** - * @Route("/statistics", name="statistics_view") - */ + #[Route(path: '/statistics', name: 'statistics_view')] public function showStatistics(StatisticsHelper $helper): Response { $this->denyAccessUnlessGranted('@tools.statistics'); diff --git a/src/Controller/ToolsController.php b/src/Controller/ToolsController.php index 3ef68b8f..c14e1a13 100644 --- a/src/Controller/ToolsController.php +++ b/src/Controller/ToolsController.php @@ -1,4 +1,7 @@ . */ - namespace App\Controller; use App\Services\Attachments\AttachmentPathResolver; @@ -32,14 +34,10 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Generator\UrlGenerator; -/** - * @Route("/tools") - */ +#[Route(path: '/tools')] class ToolsController extends AbstractController { - /** - * @Route("/reel_calc", name="tools_reel_calculator") - */ + #[Route(path: '/reel_calc', name: 'tools_reel_calculator')] public function reelCalculator(): Response { $this->denyAccessUnlessGranted('@tools.reel_calculator'); @@ -47,9 +45,7 @@ class ToolsController extends AbstractController return $this->render('tools/reel_calculator/reel_calculator.html.twig'); } - /** - * @Route("/server_infos", name="tools_server_infos") - */ + #[Route(path: '/server_infos', name: 'tools_server_infos')] public function systemInfos(GitVersionInfo $versionInfo, DBInfoHelper $DBInfoHelper, AttachmentSubmitHandler $attachmentSubmitHandler): Response { @@ -65,7 +61,7 @@ class ToolsController extends AbstractController 'default_theme' => $this->getParameter('partdb.global_theme'), 'enabled_locales' => $this->getParameter('partdb.locale_menu'), 'demo_mode' => $this->getParameter('partdb.demo_mode'), - 'gpdr_compliance' => $this->getParameter('partdb.gpdr_compliance'), + 'gpdr_compliance' => $this->getParameter('partdb.gdpr_compliance'), 'use_gravatar' => $this->getParameter('partdb.users.use_gravatar'), 'email_password_reset' => $this->getParameter('partdb.users.email_pw_reset'), 'enviroment' => $this->getParameter('kernel.environment'), @@ -83,7 +79,7 @@ class ToolsController extends AbstractController 'php_version' => PHP_VERSION, 'php_uname' => php_uname('a'), 'php_sapi' => PHP_SAPI, - 'php_extensions' => array_merge(get_loaded_extensions()), + 'php_extensions' => [...get_loaded_extensions()], 'php_opcache_enabled' => ini_get('opcache.enable'), 'php_upload_max_filesize' => ini_get('upload_max_filesize'), 'php_post_max_size' => ini_get('post_max_size'), @@ -97,33 +93,23 @@ class ToolsController extends AbstractController ]); } - /** - * @Route("/builtin_footprints", name="tools_builtin_footprints_viewer") - * @return Response - */ + #[Route(path: '/builtin_footprints', name: 'tools_builtin_footprints_viewer')] public function builtInFootprintsViewer(BuiltinAttachmentsFinder $builtinAttachmentsFinder, AttachmentURLGenerator $urlGenerator): Response { $this->denyAccessUnlessGranted('@tools.builtin_footprints_viewer'); $grouped_footprints = $builtinAttachmentsFinder->getListOfFootprintsGroupedByFolder(); - $grouped_footprints = array_map(function($group) use ($urlGenerator) { - return array_map(function($placeholder_filepath) use ($urlGenerator) { - return [ - 'filename' => basename($placeholder_filepath), - 'assets_path' => $urlGenerator->placeholderPathToAssetPath($placeholder_filepath), - ]; - }, $group); - }, $grouped_footprints); + $grouped_footprints = array_map(fn($group) => array_map(fn($placeholder_filepath) => [ + 'filename' => basename((string) $placeholder_filepath), + 'assets_path' => $urlGenerator->placeholderPathToAssetPath($placeholder_filepath), + ], $group), $grouped_footprints); return $this->render('tools/builtin_footprints_viewer/builtin_footprints_viewer.html.twig', [ 'grouped_footprints' => $grouped_footprints, ]); } - /** - * @Route("/ic_logos", name="tools_ic_logos") - * @return Response - */ + #[Route(path: '/ic_logos', name: 'tools_ic_logos')] public function icLogos(): Response { $this->denyAccessUnlessGranted('@tools.ic_logos'); diff --git a/src/Controller/TreeController.php b/src/Controller/TreeController.php index 6ab3b420..e7ce0b72 100644 --- a/src/Controller/TreeController.php +++ b/src/Controller/TreeController.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Controller; +use Symfony\Component\HttpFoundation\Response; use App\Entity\ProjectSystem\Project; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; @@ -36,21 +37,15 @@ use Symfony\Component\Routing\Annotation\Route; /** * This controller has the purpose to provide the data for all treeviews. - * - * @Route("/tree") */ +#[Route(path: '/tree')] class TreeController extends AbstractController { - protected TreeViewGenerator $treeGenerator; - - public function __construct(TreeViewGenerator $treeGenerator) + public function __construct(protected TreeViewGenerator $treeGenerator) { - $this->treeGenerator = $treeGenerator; } - /** - * @Route("/tools", name="tree_tools") - */ + #[Route(path: '/tools', name: 'tree_tools')] public function tools(ToolsTreeBuilder $builder): JsonResponse { $tree = $builder->getTree(); @@ -58,90 +53,78 @@ class TreeController extends AbstractController return new JsonResponse($tree); } - /** - * @Route("/category/{id}", name="tree_category") - * @Route("/categories", name="tree_category_root") - */ + #[Route(path: '/category/{id}', name: 'tree_category')] + #[Route(path: '/categories', name: 'tree_category_root')] public function categoryTree(?Category $category = null): JsonResponse { if ($this->isGranted('@parts.read') && $this->isGranted('@categories.read')) { $tree = $this->treeGenerator->getTreeView(Category::class, $category, 'list_parts_root'); } else { - return new JsonResponse("Access denied", 403); + return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN); } return new JsonResponse($tree); } - /** - * @Route("/footprint/{id}", name="tree_footprint") - * @Route("/footprints", name="tree_footprint_root") - */ + #[Route(path: '/footprint/{id}', name: 'tree_footprint')] + #[Route(path: '/footprints', name: 'tree_footprint_root')] public function footprintTree(?Footprint $footprint = null): JsonResponse { if ($this->isGranted('@parts.read') && $this->isGranted('@footprints.read')) { $tree = $this->treeGenerator->getTreeView(Footprint::class, $footprint, 'list_parts_root'); } else { - return new JsonResponse("Access denied", 403); + return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN); } return new JsonResponse($tree); } - /** - * @Route("/location/{id}", name="tree_location") - * @Route("/locations", name="tree_location_root") - */ + #[Route(path: '/location/{id}', name: 'tree_location')] + #[Route(path: '/locations', name: 'tree_location_root')] public function locationTree(?Storelocation $location = null): JsonResponse { if ($this->isGranted('@parts.read') && $this->isGranted('@storelocations.read')) { $tree = $this->treeGenerator->getTreeView(Storelocation::class, $location, 'list_parts_root'); } else { - return new JsonResponse("Access denied", 403); + return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN); } return new JsonResponse($tree); } - /** - * @Route("/manufacturer/{id}", name="tree_manufacturer") - * @Route("/manufacturers", name="tree_manufacturer_root") - */ + #[Route(path: '/manufacturer/{id}', name: 'tree_manufacturer')] + #[Route(path: '/manufacturers', name: 'tree_manufacturer_root')] public function manufacturerTree(?Manufacturer $manufacturer = null): JsonResponse { if ($this->isGranted('@parts.read') && $this->isGranted('@manufacturers.read')) { $tree = $this->treeGenerator->getTreeView(Manufacturer::class, $manufacturer, 'list_parts_root'); } else { - return new JsonResponse("Access denied", 403); + return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN); } return new JsonResponse($tree); } - /** - * @Route("/supplier/{id}", name="tree_supplier") - * @Route("/suppliers", name="tree_supplier_root") - */ + #[Route(path: '/supplier/{id}', name: 'tree_supplier')] + #[Route(path: '/suppliers', name: 'tree_supplier_root')] public function supplierTree(?Supplier $supplier = null): JsonResponse { if ($this->isGranted('@parts.read') && $this->isGranted('@suppliers.read')) { $tree = $this->treeGenerator->getTreeView(Supplier::class, $supplier, 'list_parts_root'); } else { - return new JsonResponse("Access denied", 403); + return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN); } return new JsonResponse($tree); } - /** - * @Route("/device/{id}", name="tree_device") - * @Route("/devices", name="tree_device_root") - */ + #[Route(path: '/device/{id}', name: 'tree_device')] + #[Route(path: '/devices', name: 'tree_device_root')] public function deviceTree(?Project $device = null): JsonResponse { if ($this->isGranted('@projects.read')) { $tree = $this->treeGenerator->getTreeView(Project::class, $device, 'devices'); } else { - return new JsonResponse("Access denied", 403); + return new JsonResponse("Access denied", Response::HTTP_FORBIDDEN); } return new JsonResponse($tree); diff --git a/src/Controller/TypeaheadController.php b/src/Controller/TypeaheadController.php index c5d440d2..3c0d76e9 100644 --- a/src/Controller/TypeaheadController.php +++ b/src/Controller/TypeaheadController.php @@ -22,6 +22,10 @@ declare(strict_types=1); namespace App\Controller; +use Symfony\Component\HttpFoundation\Response; +use App\Entity\Attachments\Attachment; +use App\Entity\Parts\Category; +use App\Entity\Parts\Footprint; use App\Entity\Parameters\AttachmentTypeParameter; use App\Entity\Parameters\CategoryParameter; use App\Entity\Parameters\ProjectParameter; @@ -51,23 +55,15 @@ use Symfony\Component\Serializer\Serializer; /** * In this controller the endpoints for the typeaheads are collected. - * - * @Route("/typeahead") */ +#[Route(path: '/typeahead')] class TypeaheadController extends AbstractController { - protected AttachmentURLGenerator $urlGenerator; - protected Packages $assets; - - public function __construct(AttachmentURLGenerator $URLGenerator, Packages $assets) + public function __construct(protected AttachmentURLGenerator $urlGenerator, protected Packages $assets) { - $this->urlGenerator = $URLGenerator; - $this->assets = $assets; } - /** - * @Route("/builtInResources/search", name="typeahead_builtInRessources") - */ + #[Route(path: '/builtInResources/search', name: 'typeahead_builtInRessources')] public function builtInResources(Request $request, BuiltinAttachmentsFinder $finder): JsonResponse { $query = $request->get('query'); @@ -91,51 +87,32 @@ class TypeaheadController extends AbstractController $serializer = new Serializer($normalizers, $encoders); $data = $serializer->serialize($result, 'json'); - return new JsonResponse($data, 200, [], true); + return new JsonResponse($data, Response::HTTP_OK, [], true); } /** * This function map the parameter type to the class, so we can access its repository - * @param string $type * @return class-string */ private function typeToParameterClass(string $type): string { - switch ($type) { - case 'category': - return CategoryParameter::class; - case 'part': - return PartParameter::class; - case 'device': - return ProjectParameter::class; - case 'footprint': - return FootprintParameter::class; - case 'manufacturer': - return ManufacturerParameter::class; - case 'storelocation': - return StorelocationParameter::class; - case 'supplier': - return SupplierParameter::class; - case 'attachment_type': - return AttachmentTypeParameter::class; - case 'group': - return GroupParameter::class; - case 'measurement_unit': - return MeasurementUnitParameter::class; - case 'currency': - return Currency::class; - - default: - throw new \InvalidArgumentException('Invalid parameter type: '.$type); - } + return match ($type) { + 'category' => CategoryParameter::class, + 'part' => PartParameter::class, + 'device' => ProjectParameter::class, + 'footprint' => FootprintParameter::class, + 'manufacturer' => ManufacturerParameter::class, + 'storelocation' => StorelocationParameter::class, + 'supplier' => SupplierParameter::class, + 'attachment_type' => AttachmentTypeParameter::class, + 'group' => GroupParameter::class, + 'measurement_unit' => MeasurementUnitParameter::class, + 'currency' => Currency::class, + default => throw new \InvalidArgumentException('Invalid parameter type: '.$type), + }; } - /** - * @Route("/parts/search/{query}", name="typeahead_parts") - * @param string $query - * @param EntityManagerInterface $entityManager - * @return JsonResponse - */ + #[Route(path: '/parts/search/{query}', name: 'typeahead_parts')] public function parts(EntityManagerInterface $entityManager, PartPreviewGenerator $previewGenerator, AttachmentURLGenerator $attachmentURLGenerator, string $query = ""): JsonResponse { @@ -149,7 +126,7 @@ class TypeaheadController extends AbstractController foreach ($parts as $part) { //Determine the picture to show: $preview_attachment = $previewGenerator->getTablePreviewAttachment($part); - if($preview_attachment !== null) { + if($preview_attachment instanceof Attachment) { $preview_url = $attachmentURLGenerator->getThumbnailURL($preview_attachment, 'thumbnail_sm'); } else { $preview_url = ''; @@ -159,8 +136,8 @@ class TypeaheadController extends AbstractController $data[] = [ 'id' => $part->getID(), 'name' => $part->getName(), - 'category' => $part->getCategory() ? $part->getCategory()->getName() : 'Unknown', - 'footprint' => $part->getFootprint() ? $part->getFootprint()->getName() : '', + 'category' => $part->getCategory() instanceof Category ? $part->getCategory()->getName() : 'Unknown', + 'footprint' => $part->getFootprint() instanceof Footprint ? $part->getFootprint()->getName() : '', 'description' => mb_strimwidth($part->getDescription(), 0, 127, '...'), 'image' => $preview_url, ]; @@ -169,11 +146,7 @@ class TypeaheadController extends AbstractController return new JsonResponse($data); } - /** - * @Route("/parameters/{type}/search/{query}", name="typeahead_parameters", requirements={"type" = ".+"}) - * @param string $query - * @return JsonResponse - */ + #[Route(path: '/parameters/{type}/search/{query}', name: 'typeahead_parameters', requirements: ['type' => '.+'])] public function parameters(string $type, EntityManagerInterface $entityManager, string $query = ""): JsonResponse { $class = $this->typeToParameterClass($type); @@ -190,9 +163,7 @@ class TypeaheadController extends AbstractController return new JsonResponse($data); } - /** - * @Route("/tags/search/{query}", name="typeahead_tags", requirements={"query"= ".+"}) - */ + #[Route(path: '/tags/search/{query}', name: 'typeahead_tags', requirements: ['query' => '.+'])] public function tags(string $query, TagFinder $finder): JsonResponse { $this->denyAccessUnlessGranted('@parts.read'); @@ -209,6 +180,6 @@ class TypeaheadController extends AbstractController $serializer = new Serializer($normalizers, $encoders); $data = $serializer->serialize($array, 'json'); - return new JsonResponse($data, 200, [], true); + return new JsonResponse($data, Response::HTTP_OK, [], true); } } diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 9669a3a6..9332b6d3 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Controller; +use App\Controller\AdminPages\BaseAdminController; use App\DataTables\LogDataTable; use App\Entity\Attachments\UserAttachment; use App\Entity\Base\AbstractNamedDBElement; @@ -46,11 +47,8 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; -/** - * @Route("/user") - * Class UserController - */ -class UserController extends AdminPages\BaseAdminController +#[Route(path: '/user')] +class UserController extends BaseAdminController { protected string $entity_class = User::class; protected string $twig_template = 'admin/user_admin.html.twig'; @@ -76,11 +74,11 @@ class UserController extends AdminPages\BaseAdminController } /** - * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="user_edit") - * @Route("/{id}/", requirements={"id"="\d+"}) * * @throws Exception */ + #[Route(path: '/{id}/edit/{timestamp}', requirements: ['id' => '\d+'], name: 'user_edit')] + #[Route(path: '/{id}/', requirements: ['id' => '\d+'])] public function edit(User $entity, Request $request, EntityManagerInterface $em, PermissionPresetsHelper $permissionPresetsHelper, PermissionSchemaUpdater $permissionSchemaUpdater, ?string $timestamp = null): Response { //Do an upgrade of the permission schema if needed (so the user can see the permissions a user get on next request (even if it was not done yet) @@ -90,7 +88,7 @@ class UserController extends AdminPages\BaseAdminController if ($request->request->has('reset_2fa')) { //Check if the admin has the needed permissions $this->denyAccessUnlessGranted('set_password', $entity); - if ($this->isCsrfTokenValid('reset_2fa'.$entity->getId(), $request->request->get('_token'))) { + if ($this->isCsrfTokenValid('reset_2fa'.$entity->getID(), $request->request->get('_token'))) { //Disable Google authenticator $entity->setGoogleAuthenticatorSecret(null); $entity->setBackupCodes([]); @@ -98,7 +96,7 @@ class UserController extends AdminPages\BaseAdminController foreach ($entity->getLegacyU2FKeys() as $key) { $em->remove($key); } - foreach ($entity->getWebAuthnKeys() as $key) { + foreach ($entity->getWebauthnKeys() as $key) { $em->remove($key); } //Invalidate trusted devices @@ -117,7 +115,7 @@ class UserController extends AdminPages\BaseAdminController //Handle permissions presets if ($request->request->has('permission_preset')) { $this->denyAccessUnlessGranted('edit_permissions', $entity); - if ($this->isCsrfTokenValid('reset_2fa'.$entity->getId(), $request->request->get('_token'))) { + if ($this->isCsrfTokenValid('reset_2fa'.$entity->getID(), $request->request->get('_token'))) { $preset = $request->request->get('permission_preset'); $permissionPresetsHelper->applyPreset($entity, $preset); @@ -148,19 +146,15 @@ class UserController extends AdminPages\BaseAdminController return true; } - /** - * @Route("/new", name="user_new") - * @Route("/{id}/clone", name="user_clone") - * @Route("/") - */ + #[Route(path: '/new', name: 'user_new')] + #[Route(path: '/{id}/clone', name: 'user_clone')] + #[Route(path: '/')] public function new(Request $request, EntityManagerInterface $em, EntityImporter $importer, ?User $entity = null): Response { return $this->_new($request, $em, $importer, $entity); } - /** - * @Route("/{id}", name="user_delete", methods={"DELETE"}, requirements={"id"="\d+"}) - */ + #[Route(path: '/{id}', name: 'user_delete', methods: ['DELETE'], requirements: ['id' => '\d+'])] public function delete(Request $request, User $entity, StructuralElementRecursionHelper $recursionHelper): RedirectResponse { if (User::ID_ANONYMOUS === $entity->getID()) { @@ -170,30 +164,24 @@ class UserController extends AdminPages\BaseAdminController return $this->_delete($request, $entity, $recursionHelper); } - /** - * @Route("/export", name="user_export_all") - */ + #[Route(path: '/export', name: 'user_export_all')] public function exportAll(EntityManagerInterface $em, EntityExporter $exporter, Request $request): Response { return $this->_exportAll($em, $exporter, $request); } - /** - * @Route("/{id}/export", name="user_export") - */ + #[Route(path: '/{id}/export', name: 'user_export')] public function exportEntity(User $entity, EntityExporter $exporter, Request $request): Response { return $this->_exportEntity($entity, $exporter, $request); } - /** - * @Route("/info", name="user_info_self") - * @Route("/{id}/info", name="user_info") - */ + #[Route(path: '/info', name: 'user_info_self')] + #[Route(path: '/{id}/info', name: 'user_info')] public function userInfo(?User $user, Packages $packages, Request $request, DataTableFactory $dataTableFactory): Response { //If no user id was passed, then we show info about the current user - if (null === $user) { + if (!$user instanceof User) { $tmp = $this->getUser(); if (!$tmp instanceof User) { throw new InvalidArgumentException('Userinfo only works for database users!'); @@ -229,7 +217,7 @@ class UserController extends AdminPages\BaseAdminController 'data' => $user, ]); - return $this->renderForm('users/user_info.html.twig', [ + return $this->render('users/user_info.html.twig', [ 'user' => $user, 'form' => $builder->getForm(), 'datatable' => $table ?? null, diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index c1c38d6b..4c721bb8 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Controller; +use App\Entity\Attachments\Attachment; use App\Entity\UserSystem\U2FKey; use App\Entity\UserSystem\User; use App\Entity\UserSystem\WebauthnKey; @@ -51,28 +52,21 @@ use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Security\Core\Validator\Constraints\UserPassword; use Symfony\Component\Validator\Constraints\Length; -/** - * @Route("/user") - */ +#[Route(path: '/user')] class UserSettingsController extends AbstractController { - protected bool $demo_mode; - /** * @var EventDispatcher|EventDispatcherInterface */ protected $eventDispatcher; - public function __construct(bool $demo_mode, EventDispatcherInterface $eventDispatcher) + public function __construct(protected bool $demo_mode, EventDispatcherInterface $eventDispatcher) { - $this->demo_mode = $demo_mode; $this->eventDispatcher = $eventDispatcher; } - /** - * @Route("/2fa_backup_codes", name="show_backup_codes") - */ - public function showBackupCodes() + #[Route(path: '/2fa_backup_codes', name: 'show_backup_codes')] + public function showBackupCodes(): Response { $user = $this->getUser(); @@ -80,14 +74,14 @@ class UserSettingsController extends AbstractController $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); if (!$user instanceof User) { - return new RuntimeException('This controller only works only for Part-DB User objects!'); + throw new RuntimeException('This controller only works only for Part-DB User objects!'); } if ($user->isSamlUser()) { throw new RuntimeException('You can not remove U2F keys from SAML users!'); } - if (empty($user->getBackupCodes())) { + if ($user->getBackupCodes() === []) { $this->addFlash('error', 'tfa_backup.no_codes_enabled'); throw new RuntimeException('You do not have any backup codes enabled, therefore you can not view them!'); @@ -98,9 +92,7 @@ class UserSettingsController extends AbstractController ]); } - /** - * @Route("/u2f_delete", name="u2f_delete", methods={"DELETE"}) - */ + #[Route(path: '/u2f_delete', name: 'u2f_delete', methods: ['DELETE'])] public function removeU2FToken(Request $request, EntityManagerInterface $entityManager, BackupCodeManager $backupCodeManager): RedirectResponse { if ($this->demo_mode) { @@ -120,56 +112,50 @@ class UserSettingsController extends AbstractController throw new RuntimeException('You can not remove U2F keys from SAML users!'); } - if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->request->get('_token'))) { + if ($this->isCsrfTokenValid('delete'.$user->getID(), $request->request->get('_token'))) { //Handle U2F key removal if ($request->request->has('key_id')) { $key_id = $request->request->get('key_id'); $key_repo = $entityManager->getRepository(U2FKey::class); /** @var U2FKey|null $u2f */ $u2f = $key_repo->find($key_id); - if (null === $u2f) { + if (!$u2f instanceof U2FKey) { $this->addFlash('danger', 'tfa_u2f.u2f_delete.not_existing'); return $this->redirectToRoute('user_settings'); } - //User can only delete its own U2F keys if ($u2f->getUser() !== $user) { $this->addFlash('danger', 'tfa_u2f.u2f_delete.access_denied'); return $this->redirectToRoute('user_settings'); } - $backupCodeManager->disableBackupCodesIfUnused($user); $entityManager->remove($u2f); $entityManager->flush(); $this->addFlash('success', 'tfa.u2f.u2f_delete.success'); - $security_event = new SecurityEvent($user); $this->eventDispatcher->dispatch($security_event, SecurityEvents::U2F_REMOVED); - } else if ($request->request->has('webauthn_key_id')) { + } elseif ($request->request->has('webauthn_key_id')) { $key_id = $request->request->get('webauthn_key_id'); $key_repo = $entityManager->getRepository(WebauthnKey::class); /** @var WebauthnKey|null $key */ $key = $key_repo->find($key_id); - if (null === $key) { + if (!$key instanceof WebauthnKey) { $this->addFlash('error', 'tfa_u2f.u2f_delete.not_existing'); return $this->redirectToRoute('user_settings'); } - //User can only delete its own U2F keys if ($key->getUser() !== $user) { $this->addFlash('error', 'tfa_u2f.u2f_delete.access_denied'); return $this->redirectToRoute('user_settings'); } - $backupCodeManager->disableBackupCodesIfUnused($user); $entityManager->remove($key); $entityManager->flush(); $this->addFlash('success', 'tfa.u2f.u2f_delete.success'); - $security_event = new SecurityEvent($user); $this->eventDispatcher->dispatch($security_event, SecurityEvents::U2F_REMOVED); } @@ -180,12 +166,8 @@ class UserSettingsController extends AbstractController return $this->redirectToRoute('user_settings'); } - /** - * @Route("/invalidate_trustedDevices", name="tfa_trustedDevices_invalidate", methods={"DELETE"}) - * - * @return RuntimeException|RedirectResponse - */ - public function resetTrustedDevices(Request $request, EntityManagerInterface $entityManager) + #[Route(path: '/invalidate_trustedDevices', name: 'tfa_trustedDevices_invalidate', methods: ['DELETE'])] + public function resetTrustedDevices(Request $request, EntityManagerInterface $entityManager): \RuntimeException|RedirectResponse { if ($this->demo_mode) { throw new RuntimeException('You can not do 2FA things in demo mode'); @@ -204,7 +186,7 @@ class UserSettingsController extends AbstractController throw new RuntimeException('You can not remove U2F keys from SAML users!'); } - if ($this->isCsrfTokenValid('devices_reset'.$user->getId(), $request->request->get('_token'))) { + if ($this->isCsrfTokenValid('devices_reset'.$user->getID(), $request->request->get('_token'))) { $user->invalidateTrustedDeviceTokens(); $entityManager->flush(); $this->addFlash('success', 'tfa_trustedDevice.invalidate.success'); @@ -219,11 +201,10 @@ class UserSettingsController extends AbstractController } /** - * @Route("/settings", name="user_settings") - * * @return RedirectResponse|Response */ - public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordHasherInterface $passwordEncoder, GoogleAuthenticator $googleAuthenticator, BackupCodeManager $backupCodeManager, FormFactoryInterface $formFactory, UserAvatarHelper $avatarHelper) + #[Route(path: '/settings', name: 'user_settings')] + public function userSettings(Request $request, EntityManagerInterface $em, UserPasswordHasherInterface $passwordEncoder, GoogleAuthenticator $googleAuthenticator, BackupCodeManager $backupCodeManager, FormFactoryInterface $formFactory, UserAvatarHelper $avatarHelper): RedirectResponse|Response { /** @var User $user */ $user = $this->getUser(); @@ -262,13 +243,11 @@ class UserSettingsController extends AbstractController } /** @var Form $form We need a form implementation for the next calls */ - if ($form->getClickedButton() && 'remove_avatar' === $form->getClickedButton()->getName()) { - //Remove the avatar attachment from the user if requested - if ($user->getMasterPictureAttachment() !== null) { - $em->remove($user->getMasterPictureAttachment()); - $user->setMasterPictureAttachment(null); - $page_need_reload = true; - } + //Remove the avatar attachment from the user if requested + if ($form->getClickedButton() && 'remove_avatar' === $form->getClickedButton()->getName() && $user->getMasterPictureAttachment() instanceof Attachment) { + $em->remove($user->getMasterPictureAttachment()); + $user->setMasterPictureAttachment(null); + $page_need_reload = true; } $em->flush(); @@ -346,7 +325,7 @@ class UserSettingsController extends AbstractController 'disabled' => $this->demo_mode || $user->isSamlUser(), ]); $google_enabled = $user->isGoogleAuthenticatorEnabled(); - if (!$google_enabled && !$form->isSubmitted()) { + if (!$google_enabled && !$google_form->isSubmitted()) { $user->setGoogleAuthenticatorSecret($googleAuthenticator->generateSecret()); $google_form->get('googleAuthenticatorSecret')->setData($user->getGoogleAuthenticatorSecret()); } @@ -382,7 +361,7 @@ class UserSettingsController extends AbstractController 'attr' => [ 'class' => 'btn-danger', ], - 'disabled' => empty($user->getBackupCodes()), + 'disabled' => $user->getBackupCodes() === [], ])->getForm(); $backup_form->handleRequest($request); @@ -397,7 +376,7 @@ class UserSettingsController extends AbstractController * Output both forms *****************************/ - return $this->renderForm('users/user_settings.html.twig', [ + return $this->render('users/user_settings.html.twig', [ 'user' => $user, 'settings_form' => $form, 'pw_form' => $pw_form, diff --git a/src/Controller/WebauthnKeyRegistrationController.php b/src/Controller/WebauthnKeyRegistrationController.php index 8a26346a..9329a3b9 100644 --- a/src/Controller/WebauthnKeyRegistrationController.php +++ b/src/Controller/WebauthnKeyRegistrationController.php @@ -1,4 +1,7 @@ . */ - namespace App\Controller; use App\Entity\UserSystem\User; @@ -27,23 +29,19 @@ use Jbtronics\TFAWebauthn\Services\TFAWebauthnRegistrationHelper; use RuntimeException; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use function Symfony\Component\Translation\t; class WebauthnKeyRegistrationController extends AbstractController { - private bool $demo_mode; - - public function __construct(bool $demo_mode) + public function __construct(private readonly bool $demo_mode) { - $this->demo_mode = $demo_mode; } - /** - * @Route("/webauthn/register", name="webauthn_register") - */ - public function register(Request $request, TFAWebauthnRegistrationHelper $registrationHelper, EntityManagerInterface $em) + #[Route(path: '/webauthn/register', name: 'webauthn_register')] + public function register(Request $request, TFAWebauthnRegistrationHelper $registrationHelper, EntityManagerInterface $em): Response { //When user change its settings, he should be logged in fully. $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY'); @@ -75,14 +73,19 @@ class WebauthnKeyRegistrationController extends AbstractController //Check the response try { $new_key = $registrationHelper->checkRegistrationResponse($webauthnResponse); - } catch (\Exception $exception) { + } catch (\Exception) { $this->addFlash('error', t('tfa_u2f.add_key.registration_error')); return $this->redirectToRoute('webauthn_register'); } + $user = $this->getUser(); + if (!$user instanceof User) { + throw new RuntimeException('This controller only works only for Part-DB User objects!'); + } + $keyEntity = WebauthnKey::fromRegistration($new_key); $keyEntity->setName($keyName); - $keyEntity->setUser($this->getUser()); + $keyEntity->setUser($user); $em->persist($keyEntity); $em->flush(); @@ -100,4 +103,4 @@ class WebauthnKeyRegistrationController extends AbstractController ] ); } -} \ No newline at end of file +} diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php deleted file mode 100644 index dff739ae..00000000 --- a/src/DataFixtures/AppFixtures.php +++ /dev/null @@ -1,37 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\DataFixtures; - -use Doctrine\Bundle\FixturesBundle\Fixture; -use Doctrine\Persistence\ObjectManager; - -class AppFixtures extends Fixture -{ - public function load(ObjectManager $manager): void - { - // $product = new Product(); - // $manager->persist($product); - - $manager->flush(); - } -} diff --git a/src/DataFixtures/DataStructureFixtures.php b/src/DataFixtures/DataStructureFixtures.php index c7416abe..a2043bdb 100644 --- a/src/DataFixtures/DataStructureFixtures.php +++ b/src/DataFixtures/DataStructureFixtures.php @@ -31,18 +31,17 @@ use App\Entity\Parts\Manufacturer; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Storelocation; use App\Entity\Parts\Supplier; +use App\Entity\UserSystem\User; use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectManager; use InvalidArgumentException; -class DataStructureFixtures extends Fixture +class DataStructureFixtures extends Fixture implements DependentFixtureInterface { - protected EntityManagerInterface $em; - - public function __construct(EntityManagerInterface $entityManager) + public function __construct(protected EntityManagerInterface $em) { - $this->em = $entityManager; } /** @@ -52,7 +51,7 @@ class DataStructureFixtures extends Fixture { //Reset autoincrement $types = [AttachmentType::class, Project::class, Category::class, Footprint::class, Manufacturer::class, - MeasurementUnit::class, Storelocation::class, Supplier::class, ]; + MeasurementUnit::class, Storelocation::class, Supplier::class,]; foreach ($types as $type) { $this->createNodesForClass($type, $manager); @@ -109,4 +108,11 @@ class DataStructureFixtures extends Fixture $manager->persist($node2_1); $manager->persist($node1_1_1); } + + public function getDependencies(): array + { + return [ + UserFixtures::class + ]; + } } diff --git a/src/DataFixtures/GroupFixtures.php b/src/DataFixtures/GroupFixtures.php index 93e93b79..d8e54b9f 100644 --- a/src/DataFixtures/GroupFixtures.php +++ b/src/DataFixtures/GroupFixtures.php @@ -30,18 +30,12 @@ use Doctrine\Persistence\ObjectManager; class GroupFixtures extends Fixture { - public const ADMINS = 'group-admin'; - public const USERS = 'group-users'; - public const READONLY = 'group-readonly'; + final public const ADMINS = 'group-admin'; + final public const USERS = 'group-users'; + final public const READONLY = 'group-readonly'; - - private PermissionPresetsHelper $permission_presets; - private PermissionManager $permissionManager; - - public function __construct(PermissionPresetsHelper $permissionPresetsHelper, PermissionManager $permissionManager) + public function __construct(private readonly PermissionPresetsHelper $permission_presets, private readonly PermissionManager $permissionManager) { - $this->permission_presets = $permissionPresetsHelper; - $this->permissionManager = $permissionManager; } public function load(ObjectManager $manager): void diff --git a/src/DataFixtures/LabelProfileFixtures.php b/src/DataFixtures/LabelProfileFixtures.php index d2c23fad..c0eb85cd 100644 --- a/src/DataFixtures/LabelProfileFixtures.php +++ b/src/DataFixtures/LabelProfileFixtures.php @@ -41,19 +41,19 @@ declare(strict_types=1); namespace App\DataFixtures; +use App\Entity\LabelSystem\BarcodeType; use App\Entity\LabelSystem\LabelOptions; +use App\Entity\LabelSystem\LabelProcessMode; use App\Entity\LabelSystem\LabelProfile; +use App\Entity\LabelSystem\LabelSupportedElement; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectManager; class LabelProfileFixtures extends Fixture { - protected EntityManagerInterface $em; - - public function __construct(EntityManagerInterface $entityManager) + public function __construct(protected EntityManagerInterface $em) { - $this->em = $entityManager; } public function load(ObjectManager $manager): void @@ -65,8 +65,8 @@ class LabelProfileFixtures extends Fixture $option1 = new LabelOptions(); $option1->setLines("[[NAME]]\n[[DESCRIPION]]"); - $option1->setBarcodeType('none'); - $option1->setSupportedElement('part'); + $option1->setBarcodeType(BarcodeType::NONE); + $option1->setSupportedElement(LabelSupportedElement::PART); $profile1->setOptions($option1); $manager->persist($profile1); @@ -77,8 +77,8 @@ class LabelProfileFixtures extends Fixture $option2 = new LabelOptions(); $option2->setLines("[[NAME]]\n[[DESCRIPION]]"); - $option2->setBarcodeType('qr'); - $option2->setSupportedElement('part'); + $option2->setBarcodeType(BarcodeType::QR); + $option2->setSupportedElement(LabelSupportedElement::PART); $profile2->setOptions($option2); $manager->persist($profile2); @@ -89,8 +89,8 @@ class LabelProfileFixtures extends Fixture $option3 = new LabelOptions(); $option3->setLines("[[NAME]]\n[[DESCRIPION]]"); - $option3->setBarcodeType('code128'); - $option3->setSupportedElement('part_lot'); + $option3->setBarcodeType(BarcodeType::CODE128); + $option3->setSupportedElement(LabelSupportedElement::PART_LOT); $profile3->setOptions($option3); $manager->persist($profile3); @@ -101,13 +101,20 @@ class LabelProfileFixtures extends Fixture $option4 = new LabelOptions(); $option4->setLines('{{ element.name }}'); - $option4->setBarcodeType('code39'); - $option4->setSupportedElement('part'); - $option4->setLinesMode('twig'); + $option4->setBarcodeType(BarcodeType::CODE39); + $option4->setSupportedElement(LabelSupportedElement::PART); + $option4->setProcessMode(LabelProcessMode::TWIG); $profile4->setOptions($option4); $manager->persist($profile4); $manager->flush(); } + + public function getDependencies(): array + { + return [ + PartFixtures::class, + ]; + } } diff --git a/src/DataFixtures/PartFixtures.php b/src/DataFixtures/PartFixtures.php index 45789dd9..3efb8dc8 100644 --- a/src/DataFixtures/PartFixtures.php +++ b/src/DataFixtures/PartFixtures.php @@ -55,16 +55,14 @@ use App\Entity\PriceInformations\Pricedetail; use Brick\Math\BigDecimal; use DateTime; use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectManager; -class PartFixtures extends Fixture +class PartFixtures extends Fixture implements DependentFixtureInterface { - protected EntityManagerInterface $em; - - public function __construct(EntityManagerInterface $entityManager) + public function __construct(protected EntityManagerInterface $em) { - $this->em = $entityManager; } public function load(ObjectManager $manager): void @@ -135,4 +133,11 @@ class PartFixtures extends Fixture $manager->persist($part); $manager->flush(); } + + public function getDependencies(): array + { + return [ + DataStructureFixtures::class + ]; + } } diff --git a/src/DataFixtures/UserFixtures.php b/src/DataFixtures/UserFixtures.php index f9e138b6..171f671e 100644 --- a/src/DataFixtures/UserFixtures.php +++ b/src/DataFixtures/UserFixtures.php @@ -24,19 +24,15 @@ namespace App\DataFixtures; use App\Entity\UserSystem\User; use Doctrine\Bundle\FixturesBundle\Fixture; +use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\ORM\EntityManagerInterface; use Doctrine\Persistence\ObjectManager; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; -class UserFixtures extends Fixture +class UserFixtures extends Fixture implements DependentFixtureInterface { - protected UserPasswordHasherInterface $encoder; - protected EntityManagerInterface $em; - - public function __construct(UserPasswordHasherInterface $encoder, EntityManagerInterface $entityManager) + public function __construct(protected UserPasswordHasherInterface $encoder, protected EntityManagerInterface $em) { - $this->em = $entityManager; - $this->encoder = $encoder; } public function load(ObjectManager $manager): void @@ -71,4 +67,11 @@ class UserFixtures extends Fixture $manager->flush(); } + + public function getDependencies(): array + { + return [ + GroupFixtures::class, + ]; + } } diff --git a/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php b/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php index 9f6d83d0..b296c4fa 100644 --- a/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php +++ b/src/DataTables/Adapters/CustomFetchJoinORMAdapter.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Adapters; use Doctrine\ORM\QueryBuilder; @@ -37,7 +39,7 @@ use Omines\DataTablesBundle\Adapter\Doctrine\FetchJoinORMAdapter; */ class CustomFetchJoinORMAdapter extends FetchJoinORMAdapter { - public function getCount(QueryBuilder $queryBuilder, $identifier): ?int + public function getCount(QueryBuilder $queryBuilder, $identifier): int { $qb_without_group_by = clone $queryBuilder; @@ -48,6 +50,6 @@ class CustomFetchJoinORMAdapter extends FetchJoinORMAdapter $paginator = new Paginator($qb_without_group_by); - return $paginator->count(); + return $paginator->count() ?? 0; } -} \ No newline at end of file +} diff --git a/src/DataTables/AttachmentDataTable.php b/src/DataTables/AttachmentDataTable.php index dd82f5a8..62a35049 100644 --- a/src/DataTables/AttachmentDataTable.php +++ b/src/DataTables/AttachmentDataTable.php @@ -42,27 +42,14 @@ use Symfony\Contracts\Translation\TranslatorInterface; final class AttachmentDataTable implements DataTableTypeInterface { - private TranslatorInterface $translator; - private EntityURLGenerator $entityURLGenerator; - private AttachmentManager $attachmentHelper; - private ElementTypeNameGenerator $elementTypeNameGenerator; - private AttachmentURLGenerator $attachmentURLGenerator; - - public function __construct(TranslatorInterface $translator, EntityURLGenerator $entityURLGenerator, - AttachmentManager $attachmentHelper, AttachmentURLGenerator $attachmentURLGenerator, - ElementTypeNameGenerator $elementTypeNameGenerator) + public function __construct(private readonly TranslatorInterface $translator, private readonly EntityURLGenerator $entityURLGenerator, private readonly AttachmentManager $attachmentHelper, private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly ElementTypeNameGenerator $elementTypeNameGenerator) { - $this->translator = $translator; - $this->entityURLGenerator = $entityURLGenerator; - $this->attachmentHelper = $attachmentHelper; - $this->elementTypeNameGenerator = $elementTypeNameGenerator; - $this->attachmentURLGenerator = $attachmentURLGenerator; } public function configure(DataTable $dataTable, array $options): void { $dataTable->add('dont_matter', RowClassColumn::class, [ - 'render' => function ($value, Attachment $context) { + 'render' => function ($value, Attachment $context): string { //Mark attachments with missing files yellow if(!$this->attachmentHelper->isFileExisting($context)){ return 'table-warning'; @@ -75,7 +62,7 @@ final class AttachmentDataTable implements DataTableTypeInterface $dataTable->add('picture', TextColumn::class, [ 'label' => '', 'className' => 'no-colvis', - 'render' => function ($value, Attachment $context) { + 'render' => function ($value, Attachment $context): string { if ($context->isPicture() && !$context->isExternal() && $this->attachmentHelper->isFileExisting($context)) { @@ -125,25 +112,21 @@ final class AttachmentDataTable implements DataTableTypeInterface $dataTable->add('attachment_type', TextColumn::class, [ 'label' => 'attachment.table.type', 'field' => 'attachment_type.name', - 'render' => function ($value, Attachment $context) { - return sprintf( - '%s', - $this->entityURLGenerator->editURL($context->getAttachmentType()), - htmlspecialchars($value) - ); - }, + 'render' => fn($value, Attachment $context): string => sprintf( + '%s', + $this->entityURLGenerator->editURL($context->getAttachmentType()), + htmlspecialchars((string) $value) + ), ]); $dataTable->add('element', TextColumn::class, [ 'label' => 'attachment.table.element', //'propertyPath' => 'element.name', - 'render' => function ($value, Attachment $context) { - return sprintf( - '%s', - $this->entityURLGenerator->infoURL($context->getElement()), - $this->elementTypeNameGenerator->getTypeNameCombination($context->getElement(), true) - ); - }, + 'render' => fn($value, Attachment $context): string => sprintf( + '%s', + $this->entityURLGenerator->infoURL($context->getElement()), + $this->elementTypeNameGenerator->getTypeNameCombination($context->getElement(), true) + ), ]); $dataTable->add('filename', TextColumn::class, [ diff --git a/src/DataTables/Column/EntityColumn.php b/src/DataTables/Column/EntityColumn.php index df3bf3d2..35bdf547 100644 --- a/src/DataTables/Column/EntityColumn.php +++ b/src/DataTables/Column/EntityColumn.php @@ -31,13 +31,8 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface; class EntityColumn extends AbstractColumn { - protected EntityURLGenerator $urlGenerator; - protected PropertyAccessorInterface $accessor; - - public function __construct(EntityURLGenerator $URLGenerator, PropertyAccessorInterface $accessor) + public function __construct(protected EntityURLGenerator $urlGenerator, protected PropertyAccessorInterface $accessor) { - $this->urlGenerator = $URLGenerator; - $this->accessor = $accessor; } /** @@ -46,46 +41,45 @@ class EntityColumn extends AbstractColumn * @param mixed $value The single value of the column * @return mixed */ - public function normalize($value) + public function normalize($value): mixed { /** @var AbstractNamedDBElement $value */ return $value; } - public function configureOptions(OptionsResolver $resolver): self + /** + * @return $this + */ + public function configureOptions(OptionsResolver $resolver): static { parent::configureOptions($resolver); $resolver->setRequired('property'); - $resolver->setDefault('field', static function (Options $option) { - return $option['property'].'.name'; - }); + $resolver->setDefault('field', static fn(Options $option): string => $option['property'].'.name'); - $resolver->setDefault('render', function (Options $options) { - return function ($value, $context) use ($options) { - if ($this->accessor->isReadable($context, $options['property'])) { - $entity = $this->accessor->getValue($context, $options['property']); - } else { - $entity = null; + $resolver->setDefault('render', fn(Options $options) => function ($value, $context) use ($options): string { + if ($this->accessor->isReadable($context, $options['property'])) { + $entity = $this->accessor->getValue($context, $options['property']); + } else { + $entity = null; + } + + /** @var AbstractNamedDBElement|null $entity */ + + if ($entity instanceof AbstractNamedDBElement) { + if (null !== $entity->getID()) { + return sprintf( + '%s', + $this->urlGenerator->listPartsURL($entity), + htmlspecialchars($entity->getName()) + ); } - /** @var AbstractNamedDBElement|null $entity */ + return sprintf('%s', $value); + } - if (null !== $entity) { - if (null !== $entity->getID()) { - return sprintf( - '%s', - $this->urlGenerator->listPartsURL($entity), - htmlspecialchars($entity->getName()) - ); - } - - return sprintf('%s', $value); - } - - return ''; - }; + return ''; }); return $this; diff --git a/src/DataTables/Column/EnumColumn.php b/src/DataTables/Column/EnumColumn.php new file mode 100644 index 00000000..04813db9 --- /dev/null +++ b/src/DataTables/Column/EnumColumn.php @@ -0,0 +1,64 @@ +. + */ + +namespace App\DataTables\Column; + +use Omines\DataTablesBundle\Column\AbstractColumn; +use Symfony\Component\OptionsResolver\OptionsResolver; +use UnitEnum; + +/** + * @template T of UnitEnum + */ +class EnumColumn extends AbstractColumn +{ + + /** + * @phpstan-return T + */ + public function normalize($value): UnitEnum + { + if (is_a($value, $this->getEnumClass())) { + return $value; + } + + //@phpstan-ignore-next-line + return ($this->getEnumClass())::from($value); + } + + protected function configureOptions(OptionsResolver $resolver): static + { + parent::configureOptions($resolver); + + $resolver->setRequired('class'); + $resolver->setAllowedTypes('class', 'string'); + $resolver->addAllowedValues('class', enum_exists(...)); + + return $this; + } + + /** + * @return class-string + */ + public function getEnumClass(): string + { + return $this->options['class']; + } +} diff --git a/src/DataTables/Column/IconLinkColumn.php b/src/DataTables/Column/IconLinkColumn.php index d1034e56..6704cb4a 100644 --- a/src/DataTables/Column/IconLinkColumn.php +++ b/src/DataTables/Column/IconLinkColumn.php @@ -50,12 +50,15 @@ class IconLinkColumn extends AbstractColumn * @param $value * @return mixed */ - public function normalize($value) + public function normalize($value): mixed { return $value; } - public function configureOptions(OptionsResolver $resolver): self + /** + * @return $this + */ + public function configureOptions(OptionsResolver $resolver): static { parent::configureOptions($resolver); $resolver->setDefaults([ diff --git a/src/DataTables/Column/LocaleDateTimeColumn.php b/src/DataTables/Column/LocaleDateTimeColumn.php index 733c0b6c..ce8cccda 100644 --- a/src/DataTables/Column/LocaleDateTimeColumn.php +++ b/src/DataTables/Column/LocaleDateTimeColumn.php @@ -38,7 +38,6 @@ class LocaleDateTimeColumn extends AbstractColumn { /** * @param $value - * @return string * @throws Exception */ public function normalize($value): string @@ -80,6 +79,9 @@ class LocaleDateTimeColumn extends AbstractColumn ); } + /** + * @return $this + */ protected function configureOptions(OptionsResolver $resolver): self { parent::configureOptions($resolver); diff --git a/src/DataTables/Column/LogEntryExtraColumn.php b/src/DataTables/Column/LogEntryExtraColumn.php index da6b4865..e0281e42 100644 --- a/src/DataTables/Column/LogEntryExtraColumn.php +++ b/src/DataTables/Column/LogEntryExtraColumn.php @@ -28,20 +28,15 @@ use Symfony\Contracts\Translation\TranslatorInterface; class LogEntryExtraColumn extends AbstractColumn { - protected TranslatorInterface $translator; - protected LogEntryExtraFormatter $formatter; - - public function __construct(TranslatorInterface $translator, LogEntryExtraFormatter $formatter) + public function __construct(protected TranslatorInterface $translator, protected LogEntryExtraFormatter $formatter) { - $this->translator = $translator; - $this->formatter = $formatter; } /** * @param $value * @return mixed */ - public function normalize($value) + public function normalize($value): mixed { return $value; } diff --git a/src/DataTables/Column/LogEntryTargetColumn.php b/src/DataTables/Column/LogEntryTargetColumn.php index 0f61d567..272ff732 100644 --- a/src/DataTables/Column/LogEntryTargetColumn.php +++ b/src/DataTables/Column/LogEntryTargetColumn.php @@ -44,22 +44,22 @@ use Symfony\Contracts\Translation\TranslatorInterface; class LogEntryTargetColumn extends AbstractColumn { - private LogTargetHelper $logTargetHelper; - - public function __construct(LogTargetHelper $logTargetHelper) + public function __construct(private readonly LogTargetHelper $logTargetHelper) { - $this->logTargetHelper = $logTargetHelper; } /** * @param $value * @return mixed */ - public function normalize($value) + public function normalize($value): mixed { return $value; } + /** + * @return $this + */ public function configureOptions(OptionsResolver $resolver): self { parent::configureOptions($resolver); diff --git a/src/DataTables/Column/MarkdownColumn.php b/src/DataTables/Column/MarkdownColumn.php index 4e3dc9ff..9120c4c5 100644 --- a/src/DataTables/Column/MarkdownColumn.php +++ b/src/DataTables/Column/MarkdownColumn.php @@ -27,11 +27,8 @@ use Omines\DataTablesBundle\Column\AbstractColumn; class MarkdownColumn extends AbstractColumn { - protected MarkdownParser $markdown; - - public function __construct(MarkdownParser $markdown) + public function __construct(protected MarkdownParser $markdown) { - $this->markdown = $markdown; } /** @@ -40,7 +37,7 @@ class MarkdownColumn extends AbstractColumn * @param mixed $value The single value of the column * @return mixed */ - public function normalize($value) + public function normalize($value): mixed { return $this->markdown->markForRendering($value, true); } diff --git a/src/DataTables/Column/PartAttachmentsColumn.php b/src/DataTables/Column/PartAttachmentsColumn.php index 48ab3201..0787a1e0 100644 --- a/src/DataTables/Column/PartAttachmentsColumn.php +++ b/src/DataTables/Column/PartAttachmentsColumn.php @@ -33,15 +33,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class PartAttachmentsColumn extends AbstractColumn { - protected FAIconGenerator $FAIconGenerator; - protected EntityURLGenerator $urlGenerator; - protected AttachmentManager $attachmentManager; - - public function __construct(FAIconGenerator $FAIconGenerator, EntityURLGenerator $urlGenerator, AttachmentManager $attachmentManager) + public function __construct(protected FAIconGenerator $FAIconGenerator, protected EntityURLGenerator $urlGenerator, protected AttachmentManager $attachmentManager) { - $this->FAIconGenerator = $FAIconGenerator; - $this->urlGenerator = $urlGenerator; - $this->attachmentManager = $attachmentManager; } /** @@ -50,7 +43,7 @@ class PartAttachmentsColumn extends AbstractColumn * @param mixed $value The single value of the column * @return mixed */ - public function normalize($value) + public function normalize($value): mixed { return $value; } @@ -61,9 +54,7 @@ class PartAttachmentsColumn extends AbstractColumn throw new RuntimeException('$context must be a Part object!'); } $tmp = ''; - $attachments = $context->getAttachments()->filter(function (Attachment $attachment) { - return $attachment->getShowInTable() && $this->attachmentManager->isFileExisting($attachment); - }); + $attachments = $context->getAttachments()->filter(fn(Attachment $attachment) => $attachment->getShowInTable() && $this->attachmentManager->isFileExisting($attachment)); $count = 5; foreach ($attachments as $attachment) { @@ -88,6 +79,9 @@ class PartAttachmentsColumn extends AbstractColumn return $tmp; } + /** + * @return $this + */ public function configureOptions(OptionsResolver $resolver): self { parent::configureOptions($resolver); diff --git a/src/DataTables/Column/PrettyBoolColumn.php b/src/DataTables/Column/PrettyBoolColumn.php index a6f74c3c..912a9122 100644 --- a/src/DataTables/Column/PrettyBoolColumn.php +++ b/src/DataTables/Column/PrettyBoolColumn.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Column; use Omines\DataTablesBundle\Column\AbstractColumn; @@ -25,11 +27,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; class PrettyBoolColumn extends AbstractColumn { - protected TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator) + public function __construct(protected TranslatorInterface $translator) { - $this->translator = $translator; } public function normalize($value): ?bool @@ -63,4 +62,4 @@ class PrettyBoolColumn extends AbstractColumn throw new \RuntimeException('Unexpected value!'); } -} \ No newline at end of file +} diff --git a/src/DataTables/Column/RevertLogColumn.php b/src/DataTables/Column/RevertLogColumn.php index 16f3365a..6ad365c1 100644 --- a/src/DataTables/Column/RevertLogColumn.php +++ b/src/DataTables/Column/RevertLogColumn.php @@ -41,30 +41,25 @@ declare(strict_types=1); namespace App\DataTables\Column; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\LogSystem\CollectionElementDeleted; use App\Entity\LogSystem\ElementCreatedLogEntry; use App\Entity\LogSystem\ElementDeletedLogEntry; use App\Entity\LogSystem\ElementEditedLogEntry; use Omines\DataTablesBundle\Column\AbstractColumn; -use Symfony\Component\Security\Core\Security; use Symfony\Contracts\Translation\TranslatorInterface; class RevertLogColumn extends AbstractColumn { - protected TranslatorInterface $translator; - protected Security $security; - - public function __construct(TranslatorInterface $translator, Security $security) + public function __construct(protected TranslatorInterface $translator, protected Security $security) { - $this->translator = $translator; - $this->security = $security; } /** * @param $value * @return mixed */ - public function normalize($value) + public function normalize($value): mixed { return $value; } @@ -105,8 +100,6 @@ class RevertLogColumn extends AbstractColumn $this->translator->trans('log.undo.revert') ); - $tmp .= ''; - - return $tmp; + return $tmp . ''; } } diff --git a/src/DataTables/Column/RowClassColumn.php b/src/DataTables/Column/RowClassColumn.php index c583cf2f..9b7aa0a0 100644 --- a/src/DataTables/Column/RowClassColumn.php +++ b/src/DataTables/Column/RowClassColumn.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Column; use Omines\DataTablesBundle\Column\AbstractColumn; @@ -27,6 +29,9 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class RowClassColumn extends AbstractColumn { + /** + * @return $this + */ public function configureOptions(OptionsResolver $resolver): self { parent::configureOptions($resolver); @@ -42,7 +47,7 @@ class RowClassColumn extends AbstractColumn return $this; } - public function initialize(string $name, int $index, array $options, DataTable $dataTable) + public function initialize(string $name, int $index, array $options, DataTable $dataTable): void { //The field name is always "$$rowClass" as this is the name the frontend controller expects parent::initialize('$$rowClass', $index, $options, $dataTable); // TODO: Change the autogenerated stub @@ -55,4 +60,4 @@ class RowClassColumn extends AbstractColumn { return $value; } -} \ No newline at end of file +} diff --git a/src/DataTables/Column/SIUnitNumberColumn.php b/src/DataTables/Column/SIUnitNumberColumn.php index fa09e227..be50505d 100644 --- a/src/DataTables/Column/SIUnitNumberColumn.php +++ b/src/DataTables/Column/SIUnitNumberColumn.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Column; use App\Services\Formatters\SIFormatter; @@ -26,13 +28,13 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class SIUnitNumberColumn extends AbstractColumn { - protected SIFormatter $formatter; - - public function __construct(SIFormatter $formatter) + public function __construct(protected SIFormatter $formatter) { - $this->formatter = $formatter; } + /** + * @return $this + */ public function configureOptions(OptionsResolver $resolver): self { parent::configureOptions($resolver); @@ -52,4 +54,4 @@ class SIUnitNumberColumn extends AbstractColumn return htmlspecialchars($this->formatter->format((float) $value, $this->options['unit'], $this->options['precision'])); } -} \ No newline at end of file +} diff --git a/src/DataTables/Column/SelectColumn.php b/src/DataTables/Column/SelectColumn.php index c26e916c..82003a62 100644 --- a/src/DataTables/Column/SelectColumn.php +++ b/src/DataTables/Column/SelectColumn.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Column; use Omines\DataTablesBundle\Column\AbstractColumn; @@ -28,6 +30,9 @@ use Symfony\Component\OptionsResolver\OptionsResolver; */ class SelectColumn extends AbstractColumn { + /** + * @return $this + */ public function configureOptions(OptionsResolver $resolver): self { parent::configureOptions($resolver); @@ -46,7 +51,7 @@ class SelectColumn extends AbstractColumn /** * @return mixed */ - public function normalize($value) + public function normalize($value): mixed { return $value; } @@ -56,4 +61,4 @@ class SelectColumn extends AbstractColumn //Return empty string, as it this column is filled by datatables on client side return ''; } -} \ No newline at end of file +} diff --git a/src/DataTables/Column/TagsColumn.php b/src/DataTables/Column/TagsColumn.php index 4d0dee0a..49ed89c7 100644 --- a/src/DataTables/Column/TagsColumn.php +++ b/src/DataTables/Column/TagsColumn.php @@ -27,11 +27,8 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; class TagsColumn extends AbstractColumn { - protected UrlGeneratorInterface $urlGenerator; - - public function __construct(UrlGeneratorInterface $urlGenerator) + public function __construct(protected UrlGeneratorInterface $urlGenerator) { - $this->urlGenerator = $urlGenerator; } /** @@ -40,17 +37,21 @@ class TagsColumn extends AbstractColumn * @param mixed $value The single value of the column * @return mixed */ - public function normalize($value) + public function normalize($value): mixed { if (empty($value)) { return []; } - return explode(',', $value); + return explode(',', (string) $value); } public function render($tags, $context): string { + if (!is_iterable($tags)) { + throw new \LogicException('TagsColumn::render() expects an iterable'); + } + $html = ''; $count = 10; foreach ($tags as $tag) { @@ -61,7 +62,7 @@ class TagsColumn extends AbstractColumn $html .= sprintf( '%s', $this->urlGenerator->generate('part_list_tags', ['tag' => $tag]), - htmlspecialchars($tag) + htmlspecialchars((string) $tag) ); } diff --git a/src/DataTables/ErrorDataTable.php b/src/DataTables/ErrorDataTable.php index 29888f17..ea3c1e76 100644 --- a/src/DataTables/ErrorDataTable.php +++ b/src/DataTables/ErrorDataTable.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables; use App\DataTables\Column\RowClassColumn; @@ -46,7 +48,7 @@ class ErrorDataTable implements DataTableTypeInterface }); } - public function configure(DataTable $dataTable, array $options) + public function configure(DataTable $dataTable, array $options): void { $optionsResolver = new OptionsResolver(); $this->configureOptions($optionsResolver); @@ -54,16 +56,12 @@ class ErrorDataTable implements DataTableTypeInterface $dataTable ->add('dont_matter_we_only_set_color', RowClassColumn::class, [ - 'render' => function ($value, $context) { - return 'table-warning'; - }, + 'render' => fn($value, $context): string => 'table-warning', ]) ->add('error', TextColumn::class, [ 'label' => 'error_table.error', - 'render' => function ($value, $context) { - return ' ' . $value; - }, + 'render' => fn($value, $context): string => ' ' . $value, ]) ; @@ -76,10 +74,13 @@ class ErrorDataTable implements DataTableTypeInterface $dataTable->createAdapter(ArrayAdapter::class, $data); } - public static function errorTable(DataTableFactory $dataTableFactory, Request $request, $errors): Response + /** + * @param string[]|string $errors + */ + public static function errorTable(DataTableFactory $dataTableFactory, Request $request, array|string $errors): Response { $error_table = $dataTableFactory->createFromType(self::class, ['errors' => $errors]); $error_table->handleRequest($request); return $error_table->getResponse(); } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/AttachmentFilter.php b/src/DataTables/Filters/AttachmentFilter.php index 9325bd60..9f8cf094 100644 --- a/src/DataTables/Filters/AttachmentFilter.php +++ b/src/DataTables/Filters/AttachmentFilter.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters; use App\DataTables\Filters\Constraints\BooleanConstraint; @@ -35,13 +37,13 @@ class AttachmentFilter implements FilterInterface { use CompoundFilterTrait; - protected NumberConstraint $dbId; - protected InstanceOfConstraint $targetType; - protected TextConstraint $name; - protected EntityConstraint $attachmentType; - protected BooleanConstraint $showInTable; - protected DateTimeConstraint $lastModified; - protected DateTimeConstraint $addedDate; + public readonly NumberConstraint $dbId; + public readonly InstanceOfConstraint $targetType; + public readonly TextConstraint $name; + public readonly EntityConstraint $attachmentType; + public readonly BooleanConstraint $showInTable; + public readonly DateTimeConstraint $lastModified; + public readonly DateTimeConstraint $addedDate; public function __construct(NodesListBuilder $nodesListBuilder) @@ -59,68 +61,4 @@ class AttachmentFilter implements FilterInterface { $this->applyAllChildFilters($queryBuilder); } - - /** - * @return NumberConstraint - */ - public function getDbId(): NumberConstraint - { - return $this->dbId; - } - - /** - * @return TextConstraint - */ - public function getName(): TextConstraint - { - return $this->name; - } - - /** - * @return DateTimeConstraint - */ - public function getLastModified(): DateTimeConstraint - { - return $this->lastModified; - } - - /** - * @return DateTimeConstraint - */ - public function getAddedDate(): DateTimeConstraint - { - return $this->addedDate; - } - - - /** - * @return BooleanConstraint - */ - public function getShowInTable(): BooleanConstraint - { - return $this->showInTable; - } - - - /** - * @return EntityConstraint - */ - public function getAttachmentType(): EntityConstraint - { - return $this->attachmentType; - } - - /** - * @return InstanceOfConstraint - */ - public function getTargetType(): InstanceOfConstraint - { - return $this->targetType; - } - - - - - - -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/CompoundFilterTrait.php b/src/DataTables/Filters/CompoundFilterTrait.php index ba778aac..5e722841 100644 --- a/src/DataTables/Filters/CompoundFilterTrait.php +++ b/src/DataTables/Filters/CompoundFilterTrait.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters; use Doctrine\Common\Collections\Collection; @@ -37,9 +39,6 @@ trait CompoundFilterTrait $reflection = new \ReflectionClass($this); foreach ($reflection->getProperties() as $property) { - //Set property to accessible (otherwise we run into problems on PHP < 8.1) - $property->setAccessible(true); - $value = $property->getValue($this); //We only want filters (objects implementing FilterInterface) if($value instanceof FilterInterface) { @@ -60,8 +59,6 @@ trait CompoundFilterTrait /** * Applies all children filters that are declared as property of this filter using reflection. - * @param QueryBuilder $queryBuilder - * @return void */ protected function applyAllChildFilters(QueryBuilder $queryBuilder): void { @@ -72,4 +69,4 @@ trait CompoundFilterTrait $filter->apply($queryBuilder); } } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/AbstractConstraint.php b/src/DataTables/Filters/Constraints/AbstractConstraint.php index d57b6add..cbb62352 100644 --- a/src/DataTables/Filters/Constraints/AbstractConstraint.php +++ b/src/DataTables/Filters/Constraints/AbstractConstraint.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints; use App\DataTables\Filters\FilterInterface; @@ -26,11 +28,6 @@ abstract class AbstractConstraint implements FilterInterface { use FilterTrait; - /** - * @var string The property where this BooleanConstraint should apply to - */ - protected string $property; - /** * @var string */ @@ -43,9 +40,13 @@ abstract class AbstractConstraint implements FilterInterface */ abstract public function isEnabled(): bool; - public function __construct(string $property, string $identifier = null) + public function __construct( + /** + * @var string The property where this BooleanConstraint should apply to + */ + protected string $property, + string $identifier = null) { - $this->property = $property; $this->identifier = $identifier ?? $this->generateParameterIdentifier($property); } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/BooleanConstraint.php b/src/DataTables/Filters/Constraints/BooleanConstraint.php index ea3cfd6a..b3f1dc47 100644 --- a/src/DataTables/Filters/Constraints/BooleanConstraint.php +++ b/src/DataTables/Filters/Constraints/BooleanConstraint.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints; use Doctrine\ORM\QueryBuilder; class BooleanConstraint extends AbstractConstraint { - /** @var bool|null The value of our constraint */ - protected ?bool $value; - - - public function __construct(string $property, string $identifier = null, ?bool $default_value = null) + public function __construct( + string $property, + string $identifier = null, + /** @var bool|null The value of our constraint */ + protected ?bool $value = null + ) { parent::__construct($property, $identifier); - $this->value = $default_value; } /** * Gets the value of this constraint. Null means "don't filter", true means "filter for true", false means "filter for false". - * @return bool|null */ public function getValue(): ?bool { @@ -45,7 +46,6 @@ class BooleanConstraint extends AbstractConstraint /** * Sets the value of this constraint. Null means "don't filter", true means "filter for true", false means "filter for false". - * @param bool|null $value */ public function setValue(?bool $value): void { @@ -67,4 +67,4 @@ class BooleanConstraint extends AbstractConstraint $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier, '=', $this->value); } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/ChoiceConstraint.php b/src/DataTables/Filters/Constraints/ChoiceConstraint.php index 52c0739f..cce7ce2c 100644 --- a/src/DataTables/Filters/Constraints/ChoiceConstraint.php +++ b/src/DataTables/Filters/Constraints/ChoiceConstraint.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints; use Doctrine\ORM\QueryBuilder; class ChoiceConstraint extends AbstractConstraint { - public const ALLOWED_OPERATOR_VALUES = ['ANY', 'NONE']; + final public const ALLOWED_OPERATOR_VALUES = ['ANY', 'NONE']; /** * @var string[]|int[] The values to compare to */ - protected array $value; + protected array $value = []; /** * @var string The operator to use */ - protected string $operator; + protected string $operator = ""; /** * @return string[]|int[] @@ -46,7 +48,6 @@ class ChoiceConstraint extends AbstractConstraint /** * @param string[]|int[] $value - * @return ChoiceConstraint */ public function setValue(array $value): ChoiceConstraint { @@ -54,18 +55,11 @@ class ChoiceConstraint extends AbstractConstraint return $this; } - /** - * @return string - */ public function getOperator(): string { return $this->operator; } - /** - * @param string $operator - * @return ChoiceConstraint - */ public function setOperator(string $operator): ChoiceConstraint { $this->operator = $operator; @@ -76,7 +70,7 @@ class ChoiceConstraint extends AbstractConstraint public function isEnabled(): bool { - return !empty($this->operator); + return $this->operator !== '' && count($this->value) > 0; } public function apply(QueryBuilder $queryBuilder): void @@ -99,4 +93,4 @@ class ChoiceConstraint extends AbstractConstraint throw new \RuntimeException('Unknown operator '. $this->operator . ' provided. Valid operators are '. implode(', ', self::ALLOWED_OPERATOR_VALUES)); } } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/DateTimeConstraint.php b/src/DataTables/Filters/Constraints/DateTimeConstraint.php index 39eaaa1d..1ded472e 100644 --- a/src/DataTables/Filters/Constraints/DateTimeConstraint.php +++ b/src/DataTables/Filters/Constraints/DateTimeConstraint.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints; /** @@ -25,4 +27,4 @@ namespace App\DataTables\Filters\Constraints; */ class DateTimeConstraint extends NumberConstraint { -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/EntityConstraint.php b/src/DataTables/Filters/Constraints/EntityConstraint.php index facbbfea..7365890b 100644 --- a/src/DataTables/Filters/Constraints/EntityConstraint.php +++ b/src/DataTables/Filters/Constraints/EntityConstraint.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints; use App\Entity\Base\AbstractDBElement; @@ -33,46 +35,26 @@ class EntityConstraint extends AbstractConstraint private const ALLOWED_OPERATOR_VALUES_BASE = ['=', '!=']; private const ALLOWED_OPERATOR_VALUES_STRUCTURAL = ['INCLUDING_CHILDREN', 'EXCLUDING_CHILDREN']; - /** - * @var NodesListBuilder - */ - protected ?NodesListBuilder $nodesListBuilder; - - /** - * @var class-string The class to use for the comparison - */ - protected string $class; - - /** - * @var string|null The operator to use - */ - protected ?string $operator; - - /** - * @var T The value to compare to - */ - protected $value; - /** * @param NodesListBuilder|null $nodesListBuilder * @param class-string $class * @param string $property * @param string|null $identifier - * @param null $value - * @param string $operator + * @param null|T $value + * @param string|null $operator */ - public function __construct(?NodesListBuilder $nodesListBuilder, string $class, string $property, string $identifier = null, $value = null, string $operator = '') + public function __construct(protected ?NodesListBuilder $nodesListBuilder, + protected string $class, + string $property, + string $identifier = null, + protected $value = null, + protected ?string $operator = null) { - $this->nodesListBuilder = $nodesListBuilder; - $this->class = $class; - - if ($nodesListBuilder === null && $this->isStructural()) { + if (!$nodesListBuilder instanceof NodesListBuilder && $this->isStructural()) { throw new \InvalidArgumentException('NodesListBuilder must be provided for structural entities'); } parent::__construct($property, $identifier); - $this->value = $value; - $this->operator = $operator; } public function getClass(): string @@ -80,17 +62,11 @@ class EntityConstraint extends AbstractConstraint return $this->class; } - /** - * @return string|null - */ public function getOperator(): ?string { return $this->operator; } - /** - * @param string|null $operator - */ public function setOperator(?string $operator): self { $this->operator = $operator; @@ -106,9 +82,10 @@ class EntityConstraint extends AbstractConstraint } /** - * @param T|null $value + * @param AbstractDBElement|null $value + * @phpstan-param T|null $value */ - public function setValue(?AbstractDBElement $value): void + public function setValue(AbstractDBElement|null $value): void { if (!$value instanceof $this->class) { throw new \InvalidArgumentException('The value must be an instance of ' . $this->class); @@ -119,7 +96,7 @@ class EntityConstraint extends AbstractConstraint /** * Checks whether the constraints apply to a structural type or not - * @return bool + * @phpstan-assert-if-true AbstractStructuralDBElement $this->value */ public function isStructural(): bool { @@ -136,7 +113,7 @@ class EntityConstraint extends AbstractConstraint $tmp = self::ALLOWED_OPERATOR_VALUES_BASE; if ($this->isStructural()) { - $tmp = array_merge($tmp, self::ALLOWED_OPERATOR_VALUES_STRUCTURAL); + $tmp = [...$tmp, ...self::ALLOWED_OPERATOR_VALUES_STRUCTURAL]; } return $tmp; @@ -144,7 +121,7 @@ class EntityConstraint extends AbstractConstraint public function isEnabled(): bool { - return !empty($this->operator); + return $this->operator !== null && $this->operator !== ''; } public function apply(QueryBuilder $queryBuilder): void @@ -160,7 +137,7 @@ class EntityConstraint extends AbstractConstraint } //We need to handle null values differently, as they can not be compared with == or != - if ($this->value === null) { + if (!$this->value instanceof AbstractDBElement) { if($this->operator === '=' || $this->operator === 'INCLUDING_CHILDREN') { $queryBuilder->andWhere(sprintf("%s IS NULL", $this->property)); return; @@ -199,4 +176,4 @@ class EntityConstraint extends AbstractConstraint } } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/FilterTrait.php b/src/DataTables/Filters/Constraints/FilterTrait.php index 733a2217..26707841 100644 --- a/src/DataTables/Filters/Constraints/FilterTrait.php +++ b/src/DataTables/Filters/Constraints/FilterTrait.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints; use Doctrine\ORM\QueryBuilder; @@ -27,7 +29,7 @@ trait FilterTrait protected bool $useHaving = false; - public function useHaving($value = true): self + public function useHaving($value = true): static { $this->useHaving = $value; return $this; @@ -35,7 +37,6 @@ trait FilterTrait /** * Checks if the given input is an aggregateFunction like COUNT(part.partsLot) or so - * @return bool */ protected function isAggregateFunctionString(string $input): bool { @@ -44,8 +45,6 @@ trait FilterTrait /** * Generates a parameter identifier that can be used for the given property. It gives random results, to be unique, so you have to cache it. - * @param string $property - * @return string */ protected function generateParameterIdentifier(string $property): string { @@ -57,13 +56,8 @@ trait FilterTrait /** * Adds a simple constraint in the form of (property OPERATOR value) (e.g. "part.name = :name") to the given query builder. - * @param QueryBuilder $queryBuilder - * @param string $property - * @param string $comparison_operator - * @param mixed $value - * @return void */ - protected function addSimpleAndConstraint(QueryBuilder $queryBuilder, string $property, string $parameterIdentifier, string $comparison_operator, $value): void + protected function addSimpleAndConstraint(QueryBuilder $queryBuilder, string $property, string $parameterIdentifier, string $comparison_operator, mixed $value): void { if ($comparison_operator === 'IN' || $comparison_operator === 'NOT IN') { $expression = sprintf("%s %s (:%s)", $property, $comparison_operator, $parameterIdentifier); @@ -79,4 +73,4 @@ trait FilterTrait $queryBuilder->setParameter($parameterIdentifier, $value); } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/InstanceOfConstraint.php b/src/DataTables/Filters/Constraints/InstanceOfConstraint.php index e339ecc1..7fc242d7 100644 --- a/src/DataTables/Filters/Constraints/InstanceOfConstraint.php +++ b/src/DataTables/Filters/Constraints/InstanceOfConstraint.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints; use Doctrine\ORM\QueryBuilder; @@ -27,17 +29,17 @@ use Doctrine\ORM\QueryBuilder; */ class InstanceOfConstraint extends AbstractConstraint { - public const ALLOWED_OPERATOR_VALUES = ['ANY', 'NONE']; + final public const ALLOWED_OPERATOR_VALUES = ['ANY', 'NONE']; /** * @var string[] The values to compare to (fully qualified class names) */ - protected array $value; + protected array $value = []; /** * @var string The operator to use */ - protected string $operator; + protected string $operator = ""; /** * @return string[] @@ -57,16 +59,12 @@ class InstanceOfConstraint extends AbstractConstraint return $this; } - /** - * @return string - */ public function getOperator(): string { return $this->operator; } /** - * @param string $operator * @return $this */ public function setOperator(string $operator): self @@ -79,7 +77,7 @@ class InstanceOfConstraint extends AbstractConstraint public function isEnabled(): bool { - return !empty($this->operator); + return $this->operator !== '' && count($this->value) > 0; } public function apply(QueryBuilder $queryBuilder): void @@ -96,6 +94,7 @@ class InstanceOfConstraint extends AbstractConstraint $expressions = []; + /** @phpstan-ignore-next-line */ if ($this->operator === 'ANY' || $this->operator === 'NONE') { foreach($this->value as $value) { //We can not use a parameter here, as this is the only way to pass the FCQN to the query (via binded params, we would need to use ClassMetaData). See: https://github.com/doctrine/orm/issues/4462 @@ -111,4 +110,4 @@ class InstanceOfConstraint extends AbstractConstraint throw new \RuntimeException('Unknown operator '. $this->operator . ' provided. Valid operators are '. implode(', ', self::ALLOWED_OPERATOR_VALUES)); } } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/IntConstraint.php b/src/DataTables/Filters/Constraints/IntConstraint.php index 601f6aa8..3fc5cce5 100644 --- a/src/DataTables/Filters/Constraints/IntConstraint.php +++ b/src/DataTables/Filters/Constraints/IntConstraint.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints; use Doctrine\ORM\QueryBuilder; @@ -35,4 +37,4 @@ class IntConstraint extends NumberConstraint parent::apply($queryBuilder); } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/NumberConstraint.php b/src/DataTables/Filters/Constraints/NumberConstraint.php index d5066f46..9e53b8f3 100644 --- a/src/DataTables/Filters/Constraints/NumberConstraint.php +++ b/src/DataTables/Filters/Constraints/NumberConstraint.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints; use Doctrine\ORM\QueryBuilder; @@ -25,62 +27,29 @@ use RuntimeException; class NumberConstraint extends AbstractConstraint { - public const ALLOWED_OPERATOR_VALUES = ['=', '!=', '<', '>', '<=', '>=', 'BETWEEN']; + protected const ALLOWED_OPERATOR_VALUES = ['=', '!=', '<', '>', '<=', '>=', 'BETWEEN']; - - /** - * The value1 used for comparison (this is the main one used for all mono-value comparisons) - * @var float|null|int|\DateTimeInterface - */ - protected $value1; - - /** - * The second value used when operator is RANGE; this is the upper bound of the range - * @var float|null|int|\DateTimeInterface - */ - protected $value2; - - /** - * @var string|null The operator to use - */ - protected ?string $operator; - - /** - * @return float|int|null|\DateTimeInterface - */ - public function getValue1() + public function getValue1(): float|int|null|\DateTimeInterface { return $this->value1; } - /** - * @param float|int|\DateTimeInterface|null $value1 - */ - public function setValue1($value1): void + public function setValue1(float|int|\DateTimeInterface|null $value1): void { $this->value1 = $value1; } - /** - * @return float|int|null - */ - public function getValue2() + public function getValue2(): float|int|null { return $this->value2; } - /** - * @param float|int|null $value2 - */ - public function setValue2($value2): void + public function setValue2(float|int|null $value2): void { $this->value2 = $value2; } - /** - * @return string - */ - public function getOperator(): string + public function getOperator(): string|null { return $this->operator; } @@ -94,18 +63,26 @@ class NumberConstraint extends AbstractConstraint } - public function __construct(string $property, string $identifier = null, $value1 = null, string $operator = null, $value2 = null) + public function __construct( + string $property, + string $identifier = null, + /** + * The value1 used for comparison (this is the main one used for all mono-value comparisons) + */ + protected float|int|\DateTimeInterface|null $value1 = null, + protected ?string $operator = null, + /** + * The second value used when operator is RANGE; this is the upper bound of the range + */ + protected float|int|\DateTimeInterface|null $value2 = null) { parent::__construct($property, $identifier); - $this->value1 = $value1; - $this->value2 = $value2; - $this->operator = $operator; } public function isEnabled(): bool { return $this->value1 !== null - && !empty($this->operator); + && ($this->operator !== null && $this->operator !== ''); } public function apply(QueryBuilder $queryBuilder): void @@ -131,4 +108,4 @@ class NumberConstraint extends AbstractConstraint $this->addSimpleAndConstraint($queryBuilder, $this->property, $this->identifier . '2', '<=', $this->value2); } } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php index 34d5c157..3bdd7cf8 100644 --- a/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/LessThanDesiredConstraint.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints\Part; use App\DataTables\Filters\Constraints\BooleanConstraint; @@ -44,4 +46,4 @@ class LessThanDesiredConstraint extends BooleanConstraint $queryBuilder->andHaving('amountSum >= part.minamount'); } } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/Part/ParameterConstraint.php b/src/DataTables/Filters/Constraints/Part/ParameterConstraint.php index ed29653b..e68dd989 100644 --- a/src/DataTables/Filters/Constraints/Part/ParameterConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/ParameterConstraint.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints\Part; use App\DataTables\Filters\Constraints\AbstractConstraint; @@ -27,19 +29,13 @@ use Doctrine\ORM\QueryBuilder; class ParameterConstraint extends AbstractConstraint { - /** @var string */ - protected string $name; + protected string $name = ''; - /** @var string */ - protected string $symbol; + protected string $symbol = ''; - /** @var string */ - protected string $unit; + protected string $unit = ''; - /** @var TextConstraint */ protected TextConstraint $value_text; - - /** @var ParameterValueConstraint */ protected ParameterValueConstraint $value; /** @var string The alias to use for the subquery */ @@ -72,19 +68,19 @@ class ParameterConstraint extends AbstractConstraint ->from(PartParameter::class, $this->alias) ->where($this->alias . '.element = part'); - if (!empty($this->name)) { + if ($this->name !== '') { $paramName = $this->generateParameterIdentifier('params.name'); $subqb->andWhere($this->alias . '.name = :' . $paramName); $queryBuilder->setParameter($paramName, $this->name); } - if (!empty($this->symbol)) { + if ($this->symbol !== '') { $paramName = $this->generateParameterIdentifier('params.symbol'); $subqb->andWhere($this->alias . '.symbol = :' . $paramName); $queryBuilder->setParameter($paramName, $this->symbol); } - if (!empty($this->unit)) { + if ($this->unit !== '') { $paramName = $this->generateParameterIdentifier('params.unit'); $subqb->andWhere($this->alias . '.unit = :' . $paramName); $queryBuilder->setParameter($paramName, $this->unit); @@ -103,75 +99,48 @@ class ParameterConstraint extends AbstractConstraint $queryBuilder->andWhere('(' . $subqb->getDQL() . ') > 0'); } - /** - * @return string - */ public function getName(): string { return $this->name; } - /** - * @param string $name - * @return ParameterConstraint - */ public function setName(string $name): ParameterConstraint { $this->name = $name; return $this; } - /** - * @return string - */ public function getSymbol(): string { return $this->symbol; } - /** - * @param string $symbol - * @return ParameterConstraint - */ public function setSymbol(string $symbol): ParameterConstraint { $this->symbol = $symbol; return $this; } - /** - * @return string - */ public function getUnit(): string { return $this->unit; } - /** - * @param string $unit - * @return ParameterConstraint - */ public function setUnit(string $unit): ParameterConstraint { $this->unit = $unit; return $this; } - /** - * @return TextConstraint - */ public function getValueText(): TextConstraint { return $this->value_text; } - /** - * @return ParameterValueConstraint - */ public function getValue(): ParameterValueConstraint { return $this->value; } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/Part/ParameterValueConstraint.php b/src/DataTables/Filters/Constraints/Part/ParameterValueConstraint.php index 5da64098..469c18c6 100644 --- a/src/DataTables/Filters/Constraints/Part/ParameterValueConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/ParameterValueConstraint.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints\Part; use App\DataTables\Filters\Constraints\NumberConstraint; @@ -25,18 +27,14 @@ use Doctrine\ORM\QueryBuilder; class ParameterValueConstraint extends NumberConstraint { - protected string $alias; - - public const ALLOWED_OPERATOR_VALUES = ['=', '!=', '<', '>', '<=', '>=', 'BETWEEN', + protected const ALLOWED_OPERATOR_VALUES = ['=', '!=', '<', '>', '<=', '>=', 'BETWEEN', //Additional operators 'IN_RANGE', 'NOT_IN_RANGE', 'GREATER_THAN_RANGE', 'GREATER_EQUAL_RANGE', 'LESS_THAN_RANGE', 'LESS_EQUAL_RANGE', 'RANGE_IN_RANGE', 'RANGE_INTERSECT_RANGE']; /** * @param string $alias The alias which is used in the sub query of ParameterConstraint */ - public function __construct(string $alias) { - $this->alias = $alias; - + public function __construct(protected string $alias) { parent::__construct($alias . '.value_typical'); } @@ -145,4 +143,4 @@ class ParameterValueConstraint extends NumberConstraint //For all other cases use the default implementation parent::apply($queryBuilder); } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/Part/TagsConstraint.php b/src/DataTables/Filters/Constraints/Part/TagsConstraint.php index 92c78f6a..acd04745 100644 --- a/src/DataTables/Filters/Constraints/Part/TagsConstraint.php +++ b/src/DataTables/Filters/Constraints/Part/TagsConstraint.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints\Part; +use Doctrine\ORM\Query\Expr\Orx; use App\DataTables\Filters\Constraints\AbstractConstraint; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; class TagsConstraint extends AbstractConstraint { - public const ALLOWED_OPERATOR_VALUES = ['ANY', 'ALL', 'NONE']; + final public const ALLOWED_OPERATOR_VALUES = ['ANY', 'ALL', 'NONE']; /** - * @var string|null The operator to use + * @param string $value */ - protected ?string $operator; - - /** + public function __construct(string $property, string $identifier = null, /** * @var string The value to compare to */ - protected $value; - - public function __construct(string $property, string $identifier = null, $value = null, string $operator = '') + protected $value = null, /** + * @var string|null The operator to use + */ + protected ?string $operator = '') { parent::__construct($property, $identifier); - $this->value = $value; - $this->operator = $operator; } /** @@ -62,17 +62,11 @@ class TagsConstraint extends AbstractConstraint return $this; } - /** - * @return string - */ public function getValue(): string { return $this->value; } - /** - * @param string $value - */ public function setValue(string $value): self { $this->value = $value; @@ -82,7 +76,7 @@ class TagsConstraint extends AbstractConstraint public function isEnabled(): bool { return $this->value !== null - && !empty($this->operator); + && ($this->operator !== null && $this->operator !== ''); } /** @@ -96,11 +90,8 @@ class TagsConstraint extends AbstractConstraint /** * Builds an expression to query for a single tag - * @param QueryBuilder $queryBuilder - * @param string $tag - * @return Expr\Orx */ - protected function getExpressionForTag(QueryBuilder $queryBuilder, string $tag): Expr\Orx + protected function getExpressionForTag(QueryBuilder $queryBuilder, string $tag): Orx { $tag_identifier_prefix = uniqid($this->identifier . '_', false); @@ -152,4 +143,4 @@ class TagsConstraint extends AbstractConstraint return; } } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/Constraints/TextConstraint.php b/src/DataTables/Filters/Constraints/TextConstraint.php index 2b4ecea4..60f83328 100644 --- a/src/DataTables/Filters/Constraints/TextConstraint.php +++ b/src/DataTables/Filters/Constraints/TextConstraint.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters\Constraints; use Doctrine\ORM\QueryBuilder; @@ -25,23 +27,20 @@ use Doctrine\ORM\QueryBuilder; class TextConstraint extends AbstractConstraint { - public const ALLOWED_OPERATOR_VALUES = ['=', '!=', 'STARTS', 'ENDS', 'CONTAINS', 'LIKE', 'REGEX']; + final public const ALLOWED_OPERATOR_VALUES = ['=', '!=', 'STARTS', 'ENDS', 'CONTAINS', 'LIKE', 'REGEX']; /** - * @var string|null The operator to use + * @param string $value */ - protected ?string $operator; - - /** + public function __construct(string $property, string $identifier = null, /** * @var string The value to compare to */ - protected $value; - - public function __construct(string $property, string $identifier = null, $value = null, string $operator = '') + protected $value = null, /** + * @var string|null The operator to use + */ + protected ?string $operator = '') { parent::__construct($property, $identifier); - $this->value = $value; - $this->operator = $operator; } /** @@ -61,17 +60,11 @@ class TextConstraint extends AbstractConstraint return $this; } - /** - * @return string - */ public function getValue(): string { return $this->value; } - /** - * @param string $value - */ public function setValue(string $value): self { $this->value = $value; @@ -81,7 +74,7 @@ class TextConstraint extends AbstractConstraint public function isEnabled(): bool { return $this->value !== null - && !empty($this->operator); + && ($this->operator !== null && $this->operator !== ''); } public function apply(QueryBuilder $queryBuilder): void @@ -105,11 +98,11 @@ class TextConstraint extends AbstractConstraint $like_value = null; if ($this->operator === 'LIKE') { $like_value = $this->value; - } else if ($this->operator === 'STARTS') { + } elseif ($this->operator === 'STARTS') { $like_value = $this->value . '%'; - } else if ($this->operator === 'ENDS') { + } elseif ($this->operator === 'ENDS') { $like_value = '%' . $this->value; - } else if ($this->operator === 'CONTAINS') { + } elseif ($this->operator === 'CONTAINS') { $like_value = '%' . $this->value . '%'; } @@ -124,4 +117,4 @@ class TextConstraint extends AbstractConstraint $queryBuilder->setParameter($this->identifier, $this->value); } } -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/FilterInterface.php b/src/DataTables/Filters/FilterInterface.php index 1abbdc30..bead3fda 100644 --- a/src/DataTables/Filters/FilterInterface.php +++ b/src/DataTables/Filters/FilterInterface.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters; use Doctrine\ORM\QueryBuilder; @@ -31,4 +33,4 @@ interface FilterInterface * @return void */ public function apply(QueryBuilder $queryBuilder): void; -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/LogFilter.php b/src/DataTables/Filters/LogFilter.php index 86a600b0..a3bbd851 100644 --- a/src/DataTables/Filters/LogFilter.php +++ b/src/DataTables/Filters/LogFilter.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters; use App\DataTables\Filters\Constraints\ChoiceConstraint; @@ -33,13 +35,13 @@ class LogFilter implements FilterInterface { use CompoundFilterTrait; - protected DateTimeConstraint $timestamp; - protected IntConstraint $dbId; - protected ChoiceConstraint $level; - protected InstanceOfConstraint $eventType; - protected ChoiceConstraint $targetType; - protected IntConstraint $targetId; - protected EntityConstraint $user; + public readonly DateTimeConstraint $timestamp; + public readonly IntConstraint $dbId; + public readonly ChoiceConstraint $level; + public readonly InstanceOfConstraint $eventType; + public readonly ChoiceConstraint $targetType; + public readonly IntConstraint $targetId; + public readonly EntityConstraint $user; public function __construct() { @@ -57,59 +59,4 @@ class LogFilter implements FilterInterface { $this->applyAllChildFilters($queryBuilder); } - - /** - * @return DateTimeConstraint - */ - public function getTimestamp(): DateTimeConstraint - { - return $this->timestamp; - } - - /** - * @return IntConstraint|NumberConstraint - */ - public function getDbId() - { - return $this->dbId; - } - - /** - * @return ChoiceConstraint - */ - public function getLevel(): ChoiceConstraint - { - return $this->level; - } - - /** - * @return InstanceOfConstraint - */ - public function getEventType(): InstanceOfConstraint - { - return $this->eventType; - } - - /** - * @return ChoiceConstraint - */ - public function getTargetType(): ChoiceConstraint - { - return $this->targetType; - } - - /** - * @return IntConstraint - */ - public function getTargetId(): IntConstraint - { - return $this->targetId; - } - - public function getUser(): EntityConstraint - { - return $this->user; - } - - -} \ No newline at end of file +} diff --git a/src/DataTables/Filters/PartFilter.php b/src/DataTables/Filters/PartFilter.php index 2e9bdeae..3d03f00c 100644 --- a/src/DataTables/Filters/PartFilter.php +++ b/src/DataTables/Filters/PartFilter.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters; use App\DataTables\Filters\Constraints\BooleanConstraint; @@ -47,45 +49,46 @@ class PartFilter implements FilterInterface use CompoundFilterTrait; - protected IntConstraint $dbId; - protected TextConstraint $ipn; - protected TextConstraint $name; - protected TextConstraint $description; - protected TextConstraint $comment; - protected TagsConstraint $tags; - protected NumberConstraint $minAmount; - protected BooleanConstraint $favorite; - protected BooleanConstraint $needsReview; - protected NumberConstraint $mass; - protected DateTimeConstraint $lastModified; - protected DateTimeConstraint $addedDate; - protected EntityConstraint $category; - protected EntityConstraint $footprint; - protected EntityConstraint $manufacturer; - protected ChoiceConstraint $manufacturing_status; - protected EntityConstraint $supplier; - protected IntConstraint $orderdetailsCount; - protected BooleanConstraint $obsolete; - protected EntityConstraint $storelocation; - protected IntConstraint $lotCount; - protected IntConstraint $amountSum; - protected LessThanDesiredConstraint $lessThanDesired; + public readonly IntConstraint $dbId; + public readonly TextConstraint $ipn; + public readonly TextConstraint $name; + public readonly TextConstraint $description; + public readonly TextConstraint $comment; + public readonly TagsConstraint $tags; + public readonly NumberConstraint $minAmount; + public readonly BooleanConstraint $favorite; + public readonly BooleanConstraint $needsReview; + public readonly NumberConstraint $mass; + public readonly DateTimeConstraint $lastModified; + public readonly DateTimeConstraint $addedDate; + public readonly EntityConstraint $category; + public readonly EntityConstraint $footprint; + public readonly EntityConstraint $manufacturer; + public readonly ChoiceConstraint $manufacturing_status; + public readonly EntityConstraint $supplier; + public readonly IntConstraint $orderdetailsCount; + public readonly BooleanConstraint $obsolete; + public readonly EntityConstraint $storelocation; + public readonly IntConstraint $lotCount; + public readonly IntConstraint $amountSum; + public readonly LessThanDesiredConstraint $lessThanDesired; - protected BooleanConstraint $lotNeedsRefill; - protected TextConstraint $lotDescription; - protected BooleanConstraint $lotUnknownAmount; - protected DateTimeConstraint $lotExpirationDate; - protected EntityConstraint $lotOwner; + public readonly BooleanConstraint $lotNeedsRefill; + public readonly TextConstraint $lotDescription; + public readonly BooleanConstraint $lotUnknownAmount; + public readonly DateTimeConstraint $lotExpirationDate; + public readonly EntityConstraint $lotOwner; + + public readonly EntityConstraint $measurementUnit; + public readonly TextConstraint $manufacturer_product_url; + public readonly TextConstraint $manufacturer_product_number; + public readonly IntConstraint $attachmentsCount; + public readonly EntityConstraint $attachmentType; + public readonly TextConstraint $attachmentName; - protected EntityConstraint $measurementUnit; - protected TextConstraint $manufacturer_product_url; - protected TextConstraint $manufacturer_product_number; - protected IntConstraint $attachmentsCount; - protected EntityConstraint $attachmentType; - protected TextConstraint $attachmentName; /** @var ArrayCollection */ - protected ArrayCollection $parameters; - protected IntConstraint $parametersCount; + public readonly ArrayCollection $parameters; + public readonly IntConstraint $parametersCount; public function __construct(NodesListBuilder $nodesListBuilder) { @@ -143,268 +146,4 @@ class PartFilter implements FilterInterface { $this->applyAllChildFilters($queryBuilder); } - - - /** - * @return BooleanConstraint - */ - public function getFavorite(): BooleanConstraint - { - return $this->favorite; - } - - /** - * @return BooleanConstraint - */ - public function getNeedsReview(): BooleanConstraint - { - return $this->needsReview; - } - - public function getMass(): NumberConstraint - { - return $this->mass; - } - - public function getName(): TextConstraint - { - return $this->name; - } - - public function getDescription(): TextConstraint - { - return $this->description; - } - - /** - * @return DateTimeConstraint - */ - public function getLastModified(): DateTimeConstraint - { - return $this->lastModified; - } - - /** - * @return DateTimeConstraint - */ - public function getAddedDate(): DateTimeConstraint - { - return $this->addedDate; - } - - public function getCategory(): EntityConstraint - { - return $this->category; - } - - /** - * @return EntityConstraint - */ - public function getFootprint(): EntityConstraint - { - return $this->footprint; - } - - /** - * @return EntityConstraint - */ - public function getManufacturer(): EntityConstraint - { - return $this->manufacturer; - } - - /** - * @return EntityConstraint - */ - public function getSupplier(): EntityConstraint - { - return $this->supplier; - } - - /** - * @return EntityConstraint - */ - public function getStorelocation(): EntityConstraint - { - return $this->storelocation; - } - - /** - * @return EntityConstraint - */ - public function getMeasurementUnit(): EntityConstraint - { - return $this->measurementUnit; - } - - /** - * @return NumberConstraint - */ - public function getDbId(): NumberConstraint - { - return $this->dbId; - } - - public function getIpn(): TextConstraint - { - return $this->ipn; - } - - /** - * @return TextConstraint - */ - public function getComment(): TextConstraint - { - return $this->comment; - } - - /** - * @return NumberConstraint - */ - public function getMinAmount(): NumberConstraint - { - return $this->minAmount; - } - - /** - * @return TextConstraint - */ - public function getManufacturerProductUrl(): TextConstraint - { - return $this->manufacturer_product_url; - } - - /** - * @return TextConstraint - */ - public function getManufacturerProductNumber(): TextConstraint - { - return $this->manufacturer_product_number; - } - - public function getLotCount(): NumberConstraint - { - return $this->lotCount; - } - - /** - * @return EntityConstraint - */ - public function getLotOwner(): EntityConstraint - { - return $this->lotOwner; - } - - /** - * @return TagsConstraint - */ - public function getTags(): TagsConstraint - { - return $this->tags; - } - - /** - * @return IntConstraint - */ - public function getOrderdetailsCount(): IntConstraint - { - return $this->orderdetailsCount; - } - - /** - * @return IntConstraint - */ - public function getAttachmentsCount(): IntConstraint - { - return $this->attachmentsCount; - } - - /** - * @return BooleanConstraint - */ - public function getLotNeedsRefill(): BooleanConstraint - { - return $this->lotNeedsRefill; - } - - /** - * @return BooleanConstraint - */ - public function getLotUnknownAmount(): BooleanConstraint - { - return $this->lotUnknownAmount; - } - - /** - * @return DateTimeConstraint - */ - public function getLotExpirationDate(): DateTimeConstraint - { - return $this->lotExpirationDate; - } - - /** - * @return EntityConstraint - */ - public function getAttachmentType(): EntityConstraint - { - return $this->attachmentType; - } - - /** - * @return TextConstraint - */ - public function getAttachmentName(): TextConstraint - { - return $this->attachmentName; - } - - public function getManufacturingStatus(): ChoiceConstraint - { - return $this->manufacturing_status; - } - - public function getAmountSum(): NumberConstraint - { - return $this->amountSum; - } - - /** - * @return ArrayCollection - */ - public function getParameters(): ArrayCollection - { - return $this->parameters; - } - - public function getParametersCount(): IntConstraint - { - return $this->parametersCount; - } - - /** - * @return TextConstraint - */ - public function getLotDescription(): TextConstraint - { - return $this->lotDescription; - } - - /** - * @return BooleanConstraint - */ - public function getObsolete(): BooleanConstraint - { - return $this->obsolete; - } - - /** - * @return LessThanDesiredConstraint - */ - public function getLessThanDesired(): LessThanDesiredConstraint - { - return $this->lessThanDesired; - } - - } diff --git a/src/DataTables/Filters/PartSearchFilter.php b/src/DataTables/Filters/PartSearchFilter.php index ccbb71fd..b94d805a 100644 --- a/src/DataTables/Filters/PartSearchFilter.php +++ b/src/DataTables/Filters/PartSearchFilter.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Filters; use Doctrine\ORM\Query\Expr; @@ -26,9 +28,6 @@ use Doctrine\ORM\QueryBuilder; class PartSearchFilter implements FilterInterface { - /** @var string The string to query for */ - protected string $keyword; - /** @var boolean Whether to use regex for searching */ protected bool $regex = false; @@ -68,9 +67,11 @@ class PartSearchFilter implements FilterInterface /** @var bool Use Internal Part number for searching */ protected bool $ipn = true; - public function __construct(string $query) + public function __construct( + /** @var string The string to query for */ + protected string $keyword + ) { - $this->keyword = $query; } protected function getFieldsToSearch(): array @@ -122,12 +123,12 @@ class PartSearchFilter implements FilterInterface $fields_to_search = $this->getFieldsToSearch(); //If we have nothing to search for, do nothing - if (empty($fields_to_search) || empty($this->keyword)) { + if ($fields_to_search === [] || $this->keyword === '') { return; } //Convert the fields to search to a list of expressions - $expressions = array_map(function (string $field) { + $expressions = array_map(function (string $field): string { if ($this->regex) { return sprintf("REGEXP(%s, :search_query) = 1", $field); } @@ -148,162 +149,99 @@ class PartSearchFilter implements FilterInterface } } - /** - * @return string - */ public function getKeyword(): string { return $this->keyword; } - /** - * @param string $keyword - * @return PartSearchFilter - */ public function setKeyword(string $keyword): PartSearchFilter { $this->keyword = $keyword; return $this; } - /** - * @return bool - */ public function isRegex(): bool { return $this->regex; } - /** - * @param bool $regex - * @return PartSearchFilter - */ public function setRegex(bool $regex): PartSearchFilter { $this->regex = $regex; return $this; } - /** - * @return bool - */ public function isName(): bool { return $this->name; } - /** - * @param bool $name - * @return PartSearchFilter - */ public function setName(bool $name): PartSearchFilter { $this->name = $name; return $this; } - /** - * @return bool - */ public function isCategory(): bool { return $this->category; } - /** - * @param bool $category - * @return PartSearchFilter - */ public function setCategory(bool $category): PartSearchFilter { $this->category = $category; return $this; } - /** - * @return bool - */ public function isDescription(): bool { return $this->description; } - /** - * @param bool $description - * @return PartSearchFilter - */ public function setDescription(bool $description): PartSearchFilter { $this->description = $description; return $this; } - /** - * @return bool - */ public function isTags(): bool { return $this->tags; } - /** - * @param bool $tags - * @return PartSearchFilter - */ public function setTags(bool $tags): PartSearchFilter { $this->tags = $tags; return $this; } - /** - * @return bool - */ public function isStorelocation(): bool { return $this->storelocation; } - /** - * @param bool $storelocation - * @return PartSearchFilter - */ public function setStorelocation(bool $storelocation): PartSearchFilter { $this->storelocation = $storelocation; return $this; } - /** - * @return bool - */ public function isOrdernr(): bool { return $this->ordernr; } - /** - * @param bool $ordernr - * @return PartSearchFilter - */ public function setOrdernr(bool $ordernr): PartSearchFilter { $this->ordernr = $ordernr; return $this; } - /** - * @return bool - */ public function isMpn(): bool { return $this->mpn; } - /** - * @param bool $mpn - * @return PartSearchFilter - */ public function setMpn(bool $mpn): PartSearchFilter { $this->mpn = $mpn; @@ -321,72 +259,44 @@ class PartSearchFilter implements FilterInterface return $this; } - /** - * @return bool - */ public function isSupplier(): bool { return $this->supplier; } - /** - * @param bool $supplier - * @return PartSearchFilter - */ public function setSupplier(bool $supplier): PartSearchFilter { $this->supplier = $supplier; return $this; } - /** - * @return bool - */ public function isManufacturer(): bool { return $this->manufacturer; } - /** - * @param bool $manufacturer - * @return PartSearchFilter - */ public function setManufacturer(bool $manufacturer): PartSearchFilter { $this->manufacturer = $manufacturer; return $this; } - /** - * @return bool - */ public function isFootprint(): bool { return $this->footprint; } - /** - * @param bool $footprint - * @return PartSearchFilter - */ public function setFootprint(bool $footprint): PartSearchFilter { $this->footprint = $footprint; return $this; } - /** - * @return bool - */ public function isComment(): bool { return $this->comment; } - /** - * @param bool $comment - * @return PartSearchFilter - */ public function setComment(bool $comment): PartSearchFilter { $this->comment = $comment; @@ -394,4 +304,4 @@ class PartSearchFilter implements FilterInterface } -} \ No newline at end of file +} diff --git a/src/DataTables/Helpers/PartDataTableHelper.php b/src/DataTables/Helpers/PartDataTableHelper.php index b13ee813..8499e303 100644 --- a/src/DataTables/Helpers/PartDataTableHelper.php +++ b/src/DataTables/Helpers/PartDataTableHelper.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables\Helpers; +use App\Entity\ProjectSystem\Project; +use App\Entity\Attachments\Attachment; use App\Entity\Parts\Part; use App\Services\Attachments\AttachmentURLGenerator; use App\Services\Attachments\PartPreviewGenerator; @@ -31,19 +35,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class PartDataTableHelper { - private PartPreviewGenerator $previewGenerator; - private AttachmentURLGenerator $attachmentURLGenerator; - - private TranslatorInterface $translator; - private EntityURLGenerator $entityURLGenerator; - - public function __construct(PartPreviewGenerator $previewGenerator, AttachmentURLGenerator $attachmentURLGenerator, - EntityURLGenerator $entityURLGenerator, TranslatorInterface $translator) + public function __construct(private readonly PartPreviewGenerator $previewGenerator, private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly EntityURLGenerator $entityURLGenerator, private readonly TranslatorInterface $translator) { - $this->previewGenerator = $previewGenerator; - $this->attachmentURLGenerator = $attachmentURLGenerator; - $this->translator = $translator; - $this->entityURLGenerator = $entityURLGenerator; } public function renderName(Part $context): string @@ -57,7 +50,7 @@ class PartDataTableHelper if ($context->isNeedsReview()) { $icon = sprintf('', $this->translator->trans('part.needs_review.badge')); } - if ($context->getBuiltProject() !== null) { + if ($context->getBuiltProject() instanceof Project) { $icon = sprintf('', $this->translator->trans('part.info.projectBuildPart.hint') . ': ' . $context->getBuiltProject()->getName()); } @@ -74,7 +67,7 @@ class PartDataTableHelper public function renderPicture(Part $context): string { $preview_attachment = $this->previewGenerator->getTablePreviewAttachment($context); - if (null === $preview_attachment) { + if (!$preview_attachment instanceof Attachment) { return ''; } @@ -92,4 +85,4 @@ class PartDataTableHelper $title ); } -} \ No newline at end of file +} diff --git a/src/DataTables/LogDataTable.php b/src/DataTables/LogDataTable.php index bef88955..d36f819a 100644 --- a/src/DataTables/LogDataTable.php +++ b/src/DataTables/LogDataTable.php @@ -22,6 +22,9 @@ declare(strict_types=1); namespace App\DataTables; +use App\DataTables\Column\EnumColumn; +use App\Entity\LogSystem\LogTargetType; +use Symfony\Bundle\SecurityBundle\Security; use App\DataTables\Column\IconLinkColumn; use App\DataTables\Column\LocaleDateTimeColumn; use App\DataTables\Column\LogEntryExtraColumn; @@ -56,32 +59,17 @@ use Psr\Log\LogLevel; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Security\Core\Security; use Symfony\Contracts\Translation\TranslatorInterface; class LogDataTable implements DataTableTypeInterface { - protected ElementTypeNameGenerator $elementTypeNameGenerator; - protected TranslatorInterface $translator; - protected UrlGeneratorInterface $urlGenerator; - protected EntityURLGenerator $entityURLGenerator; protected LogEntryRepository $logRepo; - protected Security $security; - protected UserAvatarHelper $userAvatarHelper; - protected LogLevelHelper $logLevelHelper; - public function __construct(ElementTypeNameGenerator $elementTypeNameGenerator, TranslatorInterface $translator, - UrlGeneratorInterface $urlGenerator, EntityURLGenerator $entityURLGenerator, EntityManagerInterface $entityManager, - Security $security, UserAvatarHelper $userAvatarHelper, LogLevelHelper $logLevelHelper) + public function __construct(protected ElementTypeNameGenerator $elementTypeNameGenerator, protected TranslatorInterface $translator, + protected UrlGeneratorInterface $urlGenerator, protected EntityURLGenerator $entityURLGenerator, EntityManagerInterface $entityManager, + protected Security $security, protected UserAvatarHelper $userAvatarHelper, protected LogLevelHelper $logLevelHelper) { - $this->elementTypeNameGenerator = $elementTypeNameGenerator; - $this->translator = $translator; - $this->urlGenerator = $urlGenerator; - $this->entityURLGenerator = $entityURLGenerator; $this->logRepo = $entityManager->getRepository(AbstractLogEntry::class); - $this->security = $security; - $this->userAvatarHelper = $userAvatarHelper; - $this->logLevelHelper = $logLevelHelper; } public function configureOptions(OptionsResolver $optionsResolver): void @@ -115,21 +103,17 @@ class LogDataTable implements DataTableTypeInterface //This special $$rowClass column is used to set the row class depending on the log level. The class gets set by the frontend controller $dataTable->add('dont_matter', RowClassColumn::class, [ - 'render' => function ($value, AbstractLogEntry $context) { - return $this->logLevelHelper->logLevelToTableColorClass($context->getLevelString()); - }, + 'render' => fn($value, AbstractLogEntry $context) => $this->logLevelHelper->logLevelToTableColorClass($context->getLevelString()), ]); $dataTable->add('symbol', TextColumn::class, [ 'label' => '', 'className' => 'no-colvis', - 'render' => function ($value, AbstractLogEntry $context) { - return sprintf( - '', - $this->logLevelHelper->logLevelToIconClass($context->getLevelString()), - $context->getLevelString() - ); - }, + 'render' => fn($value, AbstractLogEntry $context): string => sprintf( + '', + $this->logLevelHelper->logLevelToIconClass($context->getLevelString()), + $context->getLevelString() + ), ]); $dataTable->add('id', TextColumn::class, [ @@ -140,12 +124,10 @@ class LogDataTable implements DataTableTypeInterface $dataTable->add('timestamp', LocaleDateTimeColumn::class, [ 'label' => 'log.timestamp', 'timeFormat' => 'medium', - 'render' => function (string $value, AbstractLogEntry $context) { - return sprintf('%s', - $this->urlGenerator->generate('log_details', ['id' => $context->getId()]), - $value - ); - } + 'render' => fn(string $value, AbstractLogEntry $context): string => sprintf('%s', + $this->urlGenerator->generate('log_details', ['id' => $context->getID()]), + $value + ) ]); $dataTable->add('type', TextColumn::class, [ @@ -157,7 +139,7 @@ class LogDataTable implements DataTableTypeInterface if ($context instanceof PartStockChangedLogEntry) { $text .= sprintf( ' (%s)', - $this->translator->trans('log.part_stock_changed.' . $context->getInstockChangeType()) + $this->translator->trans('log.part_stock_changed.' . $context->getInstockChangeType()->toExtraShortType()) ); } @@ -169,18 +151,16 @@ class LogDataTable implements DataTableTypeInterface 'label' => 'log.level', 'visible' => 'system_log' === $options['mode'], 'propertyPath' => 'levelString', - 'render' => function (string $value, AbstractLogEntry $context) { - return $this->translator->trans('log.level.'.$value); - }, + 'render' => fn(string $value, AbstractLogEntry $context) => $this->translator->trans('log.level.'.$value), ]); $dataTable->add('user', TextColumn::class, [ 'label' => 'log.user', - 'render' => function ($value, AbstractLogEntry $context) { + 'render' => function ($value, AbstractLogEntry $context): string { $user = $context->getUser(); //If user was deleted, show the info from the username field - if ($user === null) { + if (!$user instanceof User) { if ($context->isCLIEntry()) { return sprintf('%s [%s]', htmlentities($context->getCLIUsername()), @@ -208,11 +188,12 @@ class LogDataTable implements DataTableTypeInterface }, ]); - $dataTable->add('target_type', TextColumn::class, [ + $dataTable->add('target_type', EnumColumn::class, [ 'label' => 'log.target_type', 'visible' => false, - 'render' => function ($value, AbstractLogEntry $context) { - $class = $context->getTargetClass(); + 'class' => LogTargetType::class, + 'render' => function (LogTargetType $value, AbstractLogEntry $context) { + $class = $value->toClass(); if (null !== $class) { return $this->elementTypeNameGenerator->getLocalizedTypeLabel($class); } @@ -241,19 +222,17 @@ class LogDataTable implements DataTableTypeInterface ) { try { $target = $this->logRepo->getTargetElement($context); - if (null !== $target) { + if ($target instanceof AbstractDBElement) { return $this->entityURLGenerator->timeTravelURL($target, $context->getTimestamp()); } - } catch (EntityNotSupportedException $exception) { + } catch (EntityNotSupportedException) { return null; } } return null; }, - 'disabled' => function ($value, AbstractLogEntry $context) { - return !$this->security->isGranted('show_history', $context->getTargetClass()); - }, + 'disabled' => fn($value, AbstractLogEntry $context) => !$this->security->isGranted('show_history', $context->getTargetClass()), ]); $dataTable->add('actionRevert', RevertLogColumn::class, [ @@ -301,8 +280,8 @@ class LogDataTable implements DataTableTypeInterface ->andWhere('log.target_type NOT IN (:disallowed)'); $builder->setParameter('disallowed', [ - AbstractLogEntry::targetTypeClassToID(User::class), - AbstractLogEntry::targetTypeClassToID(Group::class), + LogTargetType::USER, + LogTargetType::GROUP, ]); } @@ -310,9 +289,12 @@ class LogDataTable implements DataTableTypeInterface foreach ($options['filter_elements'] as $element) { /** @var AbstractDBElement $element */ - $target_type = AbstractLogEntry::targetTypeClassToID(get_class($element)); + $target_type = LogTargetType::fromElementClass($element); $target_id = $element->getID(); - $builder->orWhere("log.target_type = ${target_type} AND log.target_id = ${target_id}"); + + $builder->orWhere('log.target_type = :filter_target_type AND log.target_id = :filter_target_id'); + $builder->setParameter('filter_target_type', $target_type); + $builder->setParameter('filter_target_id', $target_id); } } } diff --git a/src/DataTables/PartsDataTable.php b/src/DataTables/PartsDataTable.php index 8dc94ba0..82259cdd 100644 --- a/src/DataTables/PartsDataTable.php +++ b/src/DataTables/PartsDataTable.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\DataTables; +use Symfony\Bundle\SecurityBundle\Security; +use App\Entity\Parts\Storelocation; use App\DataTables\Adapters\CustomFetchJoinORMAdapter; use App\DataTables\Column\EntityColumn; use App\DataTables\Column\IconLinkColumn; @@ -49,27 +51,12 @@ use Omines\DataTablesBundle\Column\TextColumn; use Omines\DataTablesBundle\DataTable; use Omines\DataTablesBundle\DataTableTypeInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Core\Security; use Symfony\Contracts\Translation\TranslatorInterface; final class PartsDataTable implements DataTableTypeInterface { - private TranslatorInterface $translator; - private AmountFormatter $amountFormatter; - private Security $security; - - private PartDataTableHelper $partDataTableHelper; - - private EntityURLGenerator $urlGenerator; - - public function __construct(EntityURLGenerator $urlGenerator, TranslatorInterface $translator, - AmountFormatter $amountFormatter,PartDataTableHelper $partDataTableHelper, Security $security) + public function __construct(private readonly EntityURLGenerator $urlGenerator, private readonly TranslatorInterface $translator, private readonly AmountFormatter $amountFormatter, private readonly PartDataTableHelper $partDataTableHelper, private readonly Security $security) { - $this->urlGenerator = $urlGenerator; - $this->translator = $translator; - $this->amountFormatter = $amountFormatter; - $this->security = $security; - $this->partDataTableHelper = $partDataTableHelper; } public function configureOptions(OptionsResolver $optionsResolver): void @@ -92,7 +79,7 @@ final class PartsDataTable implements DataTableTypeInterface $dataTable //Color the table rows depending on the review and favorite status ->add('dont_matter', RowClassColumn::class, [ - 'render' => function ($value, Part $context) { + 'render' => function ($value, Part $context): string { if ($context->isNeedsReview()) { return 'table-secondary'; } @@ -108,15 +95,11 @@ final class PartsDataTable implements DataTableTypeInterface ->add('picture', TextColumn::class, [ 'label' => '', 'className' => 'no-colvis', - 'render' => function ($value, Part $context) { - return $this->partDataTableHelper->renderPicture($context); - }, + 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderPicture($context), ]) ->add('name', TextColumn::class, [ 'label' => $this->translator->trans('part.table.name'), - 'render' => function ($value, Part $context) { - return $this->partDataTableHelper->renderName($context); - }, + 'render' => fn($value, Part $context) => $this->partDataTableHelper->renderName($context), ]) ->add('id', TextColumn::class, [ 'label' => $this->translator->trans('part.table.id'), @@ -153,11 +136,11 @@ final class PartsDataTable implements DataTableTypeInterface $dataTable->add('storelocation', TextColumn::class, [ 'label' => $this->translator->trans('part.table.storeLocations'), 'orderField' => 'storelocations.name', - 'render' => function ($value, Part $context) { + 'render' => function ($value, Part $context): string { $tmp = []; foreach ($context->getPartLots() as $lot) { //Ignore lots without storelocation - if (null === $lot->getStorageLocation()) { + if (!$lot->getStorageLocation() instanceof Storelocation) { continue; } $tmp[] = sprintf( @@ -216,9 +199,7 @@ final class PartsDataTable implements DataTableTypeInterface ->add('minamount', TextColumn::class, [ 'label' => $this->translator->trans('part.table.minamount'), 'visible' => false, - 'render' => function ($value, Part $context) { - return htmlspecialchars($this->amountFormatter->format($value, $context->getPartUnit())); - }, + 'render' => fn($value, Part $context): string => htmlspecialchars($this->amountFormatter->format($value, $context->getPartUnit())), ]); if ($this->security->isGranted('@footprints.read')) { @@ -278,12 +259,8 @@ final class PartsDataTable implements DataTableTypeInterface ->add('edit', IconLinkColumn::class, [ 'label' => $this->translator->trans('part.table.edit'), 'visible' => false, - 'href' => function ($value, Part $context) { - return $this->urlGenerator->editURL($context); - }, - 'disabled' => function ($value, Part $context) { - return !$this->security->isGranted('edit', $context); - }, + 'href' => fn($value, Part $context) => $this->urlGenerator->editURL($context), + 'disabled' => fn($value, Part $context) => !$this->security->isGranted('edit', $context), 'title' => $this->translator->trans('part.table.edit.title'), ]) diff --git a/src/DataTables/ProjectBomEntriesDataTable.php b/src/DataTables/ProjectBomEntriesDataTable.php index eac5ae5a..4586d2f7 100644 --- a/src/DataTables/ProjectBomEntriesDataTable.php +++ b/src/DataTables/ProjectBomEntriesDataTable.php @@ -1,4 +1,7 @@ . */ - namespace App\DataTables; use App\DataTables\Column\EntityColumn; @@ -40,22 +42,12 @@ use Symfony\Contracts\Translation\TranslatorInterface; class ProjectBomEntriesDataTable implements DataTableTypeInterface { - protected TranslatorInterface $translator; - protected PartDataTableHelper $partDataTableHelper; - protected EntityURLGenerator $entityURLGenerator; - protected AmountFormatter $amountFormatter; - - public function __construct(TranslatorInterface $translator, PartDataTableHelper $partDataTableHelper, - EntityURLGenerator $entityURLGenerator, AmountFormatter $amountFormatter) + public function __construct(protected TranslatorInterface $translator, protected PartDataTableHelper $partDataTableHelper, protected EntityURLGenerator $entityURLGenerator, protected AmountFormatter $amountFormatter) { - $this->translator = $translator; - $this->partDataTableHelper = $partDataTableHelper; - $this->entityURLGenerator = $entityURLGenerator; - $this->amountFormatter = $amountFormatter; } - public function configure(DataTable $dataTable, array $options) + public function configure(DataTable $dataTable, array $options): void { $dataTable //->add('select', SelectColumn::class) @@ -63,7 +55,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'label' => '', 'className' => 'no-colvis', 'render' => function ($value, ProjectBOMEntry $context) { - if($context->getPart() === null) { + if(!$context->getPart() instanceof Part) { return ''; } return $this->partDataTableHelper->renderPicture($context->getPart()); @@ -79,9 +71,9 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'label' => $this->translator->trans('project.bom.quantity'), 'className' => 'text-center', 'orderField' => 'bom_entry.quantity', - 'render' => function ($value, ProjectBOMEntry $context) { + 'render' => function ($value, ProjectBOMEntry $context): float|string { //If we have a non-part entry, only show the rounded quantity - if ($context->getPart() === null) { + if (!$context->getPart() instanceof Part) { return round($context->getQuantity()); } //Otherwise use the unit of the part to format the quantity @@ -93,16 +85,18 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface 'label' => $this->translator->trans('part.table.name'), 'orderField' => 'part.name', 'render' => function ($value, ProjectBOMEntry $context) { - if($context->getPart() === null) { + if(!$context->getPart() instanceof Part) { return htmlspecialchars($context->getName()); } - if($context->getPart() !== null) { + if($context->getPart() instanceof Part) { $tmp = $this->partDataTableHelper->renderName($context->getPart()); - if(!empty($context->getName())) { + if($context->getName() !== null && $context->getName() !== '') { $tmp .= '
'.htmlspecialchars($context->getName()).''; } return $tmp; } + + //@phpstan-ignore-next-line throw new \Exception('This should never happen!'); }, ]) @@ -110,7 +104,7 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface ->add('description', MarkdownColumn::class, [ 'label' => $this->translator->trans('part.table.description'), 'data' => function (ProjectBOMEntry $context) { - if($context->getPart() !== null) { + if($context->getPart() instanceof Part) { return $context->getPart()->getDescription(); } //For non-part BOM entries show the comment field @@ -193,4 +187,4 @@ class ProjectBomEntriesDataTable implements DataTableTypeInterface { } -} \ No newline at end of file +} diff --git a/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php b/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php index a3fdaf1e..755fd553 100644 --- a/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php +++ b/src/Doctrine/Purger/ResetAutoIncrementORMPurger.php @@ -27,6 +27,7 @@ use Doctrine\Common\DataFixtures\Purger\PurgerInterface; use Doctrine\Common\DataFixtures\Sorter\TopologicalSorter; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\SqlitePlatform; use Doctrine\DBAL\Schema\Identifier; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Mapping\ClassMetadata; @@ -47,11 +48,8 @@ use function preg_match; */ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface { - public const PURGE_MODE_DELETE = 1; - public const PURGE_MODE_TRUNCATE = 2; - - /** @var EntityManagerInterface|null */ - private ?EntityManagerInterface $em; + final public const PURGE_MODE_DELETE = 1; + final public const PURGE_MODE_TRUNCATE = 2; /** * If the purge should be done through DELETE or TRUNCATE statements @@ -60,31 +58,26 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface */ private int $purgeMode = self::PURGE_MODE_DELETE; - /** - * Table/view names to be excluded from purge - * - * @var string[] - */ - private array $excluded; - /** * Construct new purger instance. * * @param EntityManagerInterface|null $em EntityManagerInterface instance used for persistence. * @param string[] $excluded array of table/view names to be excluded from purge */ - public function __construct(?EntityManagerInterface $em = null, array $excluded = []) + public function __construct( + private ?EntityManagerInterface $em = null, + /** + * Table/view names to be excluded from purge + */ + private readonly array $excluded = [] + ) { - $this->em = $em; - $this->excluded = $excluded; } /** * Set the purge mode * - * @param int $mode * - * @return void */ public function setPurgeMode(int $mode): void { @@ -93,8 +86,6 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface /** * Get the purge mode - * - * @return int */ public function getPurgeMode(): int { @@ -123,7 +114,7 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface $classes = []; foreach ($this->em->getMetadataFactory()->getAllMetadata() as $metadata) { - if ($metadata->isMappedSuperclass || (isset($metadata->isEmbeddedClass) && $metadata->isEmbeddedClass)) { + if ($metadata->isMappedSuperclass || ($metadata->isEmbeddedClass !== null && $metadata->isEmbeddedClass)) { continue; } @@ -143,7 +134,7 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface $class = $commitOrder[$i]; if ( - (isset($class->isEmbeddedClass) && $class->isEmbeddedClass) || + ($class->isEmbeddedClass !== null && $class->isEmbeddedClass) || $class->isMappedSuperclass || ($class->isInheritanceTypeSingleTable() && $class->name !== $class->rootEntityName) ) { @@ -172,13 +163,13 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface foreach ($orderedTables as $tbl) { // If we have a filter expression, check it and skip if necessary - if (! $emptyFilterExpression && ! preg_match($filterExpr, $tbl)) { + if (! $emptyFilterExpression && ! preg_match($filterExpr, (string) $tbl)) { continue; } // The table name might be quoted, we have to trim it // See https://github.com/Part-DB/Part-DB-server/issues/299 - $tbl = trim($tbl, '"'); + $tbl = trim((string) $tbl, '"'); $tbl = trim($tbl, '`'); // If the table is excluded, skip it as well @@ -198,7 +189,7 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface } //Reseting autoincrement is only supported on MySQL platforms - if ($platform instanceof AbstractMySQLPlatform) { + if ($platform instanceof AbstractMySQLPlatform ) { //|| $platform instanceof SqlitePlatform) { $connection->beginTransaction(); $connection->executeQuery($this->getResetAutoIncrementSQL($tbl, $platform)); } @@ -214,7 +205,14 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface { $tableIdentifier = new Identifier($tableName); - return 'ALTER TABLE '. $tableIdentifier->getQuotedName($platform) .' AUTO_INCREMENT = 1;'; + if ($platform instanceof AbstractMySQLPlatform) { + return 'ALTER TABLE '.$tableIdentifier->getQuotedName($platform).' AUTO_INCREMENT = 1;'; + } + + //This seems to cause problems somehow + /*if ($platform instanceof SqlitePlatform) { + return 'DELETE FROM `sqlite_sequence` WHERE name = \''.$tableIdentifier->getQuotedName($platform).'\';'; + }*/ } /** @@ -276,11 +274,6 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface return array_reverse($sorter->sort()); } - /** - * @param array $classes - * - * @return array - */ private function getAssociationTables(array $classes, AbstractPlatform $platform): array { $associationTables = []; @@ -310,9 +303,6 @@ class ResetAutoIncrementORMPurger implements PurgerInterface, ORMPurgerInterface return $this->em->getConfiguration()->getQuoteStrategy()->getTableName($class, $platform); } - /** - * @param array $assoc - */ private function getJoinTableName( array $assoc, ClassMetadata $class, diff --git a/src/Doctrine/Purger/ResetAutoIncrementPurgerFactory.php b/src/Doctrine/Purger/ResetAutoIncrementPurgerFactory.php index 00152718..4d78e0f0 100644 --- a/src/Doctrine/Purger/ResetAutoIncrementPurgerFactory.php +++ b/src/Doctrine/Purger/ResetAutoIncrementPurgerFactory.php @@ -1,4 +1,7 @@ . */ - namespace App\Doctrine\Purger; use Doctrine\Bundle\FixturesBundle\Purger\PurgerFactory; @@ -35,4 +37,4 @@ class ResetAutoIncrementPurgerFactory implements PurgerFactory return $purger; } -} \ No newline at end of file +} diff --git a/src/Doctrine/SQLiteRegexExtension.php b/src/Doctrine/SQLiteRegexExtension.php index 0845ffb2..b5839c63 100644 --- a/src/Doctrine/SQLiteRegexExtension.php +++ b/src/Doctrine/SQLiteRegexExtension.php @@ -1,4 +1,7 @@ . */ - namespace App\Doctrine; use App\Exceptions\InvalidRegexException; +use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener; use Doctrine\Bundle\DoctrineBundle\EventSubscriber\EventSubscriberInterface; use Doctrine\DBAL\Event\ConnectionEventArgs; use Doctrine\DBAL\Events; @@ -31,7 +34,8 @@ use Doctrine\DBAL\Platforms\SqlitePlatform; * As a PHP callback is called for every entry to compare it is most likely much slower than using regex on MySQL. * But as regex is not often used, this should be fine for most use cases, also it is almost impossible to implement a better solution. */ -class SQLiteRegexExtension implements EventSubscriberInterface +#[AsDoctrineListener(Events::postConnect)] +class SQLiteRegexExtension { public function postConnect(ConnectionEventArgs $eventArgs): void { @@ -45,7 +49,7 @@ class SQLiteRegexExtension implements EventSubscriberInterface if($native_connection instanceof \PDO && method_exists($native_connection, 'sqliteCreateFunction' )) { $native_connection->sqliteCreateFunction('REGEXP', function ($pattern, $value) { try { - return (false !== mb_ereg($pattern, $value)) ? 1 : 0; + return (mb_ereg($pattern, $value)) ? 1 : 0; } catch (\ErrorException $e) { throw InvalidRegexException::fromMBRegexError($e); } @@ -53,11 +57,4 @@ class SQLiteRegexExtension implements EventSubscriberInterface } } } - - public function getSubscribedEvents(): array - { - return[ - Events::postConnect - ]; - } -} \ No newline at end of file +} diff --git a/src/Doctrine/SetSQLMode/SetSQLModeMiddlewareDriver.php b/src/Doctrine/SetSQLMode/SetSQLModeMiddlewareDriver.php index 4b91ab57..5501b231 100644 --- a/src/Doctrine/SetSQLMode/SetSQLModeMiddlewareDriver.php +++ b/src/Doctrine/SetSQLMode/SetSQLModeMiddlewareDriver.php @@ -1,4 +1,7 @@ . */ - namespace App\Doctrine\SetSQLMode; +use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Driver\Middleware\AbstractDriverMiddleware; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; @@ -29,7 +32,7 @@ use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; */ class SetSQLModeMiddlewareDriver extends AbstractDriverMiddleware { - public function connect(array $params): \Doctrine\DBAL\Driver\Connection + public function connect(array $params): Connection { //Only set this on MySQL connections, as other databases don't support this parameter if($this->getDatabasePlatform() instanceof AbstractMySQLPlatform) { @@ -39,4 +42,4 @@ class SetSQLModeMiddlewareDriver extends AbstractDriverMiddleware return parent::connect($params); } -} \ No newline at end of file +} diff --git a/src/Doctrine/SetSQLMode/SetSQLModeMiddlewareWrapper.php b/src/Doctrine/SetSQLMode/SetSQLModeMiddlewareWrapper.php index 20632b56..34320fa5 100644 --- a/src/Doctrine/SetSQLMode/SetSQLModeMiddlewareWrapper.php +++ b/src/Doctrine/SetSQLMode/SetSQLModeMiddlewareWrapper.php @@ -1,4 +1,7 @@ . */ - namespace App\Doctrine\SetSQLMode; use Doctrine\DBAL\Driver; @@ -33,4 +35,4 @@ class SetSQLModeMiddlewareWrapper implements Middleware { return new SetSQLModeMiddlewareDriver($driver); } -} \ No newline at end of file +} diff --git a/src/Doctrine/Types/BigDecimalType.php b/src/Doctrine/Types/BigDecimalType.php index f1522857..a9f796dd 100644 --- a/src/Doctrine/Types/BigDecimalType.php +++ b/src/Doctrine/Types/BigDecimalType.php @@ -1,4 +1,7 @@ . */ - namespace App\Doctrine\Types; use Brick\Math\BigDecimal; @@ -27,24 +29,21 @@ use Doctrine\DBAL\Types\Type; class BigDecimalType extends Type { - public const BIG_DECIMAL = 'big_decimal'; + final public const BIG_DECIMAL = 'big_decimal'; public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string { return $platform->getDecimalTypeDeclarationSQL($fieldDeclaration); } - /** - * @param string|null $value - * - * @return BigDecimal|BigNumber|mixed - */ - public function convertToPHPValue($value, AbstractPlatform $platform) + public function convertToPHPValue(mixed $value, AbstractPlatform $platform): ?BigNumber { if (null === $value) { return null; } + + return BigDecimal::of($value); } @@ -53,7 +52,7 @@ class BigDecimalType extends Type */ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string { - if (null === $value) { + if (!$value instanceof BigDecimal) { return null; } diff --git a/src/Doctrine/Types/TinyIntType.php b/src/Doctrine/Types/TinyIntType.php index 951417db..9b2fc7a9 100644 --- a/src/Doctrine/Types/TinyIntType.php +++ b/src/Doctrine/Types/TinyIntType.php @@ -1,4 +1,7 @@ . */ - namespace App\Doctrine\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; @@ -44,4 +46,4 @@ class TinyIntType extends Type //We use the comment, so that doctrine migrations can properly detect, that nothing has changed and no migration is needed. return true; } -} \ No newline at end of file +} diff --git a/src/Doctrine/Types/UTCDateTimeType.php b/src/Doctrine/Types/UTCDateTimeType.php index c9fe215b..c7140252 100644 --- a/src/Doctrine/Types/UTCDateTimeType.php +++ b/src/Doctrine/Types/UTCDateTimeType.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Doctrine\Types; use DateTime; +use DateTimeInterface; use DateTimeZone; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; @@ -47,7 +48,7 @@ class UTCDateTimeType extends DateTimeType */ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string { - if (!self::$utc_timezone) { + if (!self::$utc_timezone instanceof \DateTimeZone) { self::$utc_timezone = new DateTimeZone('UTC'); } @@ -58,9 +59,18 @@ class UTCDateTimeType extends DateTimeType return parent::convertToDatabaseValue($value, $platform); } - public function convertToPHPValue($value, AbstractPlatform $platform): ?DateTime + /** + * {@inheritDoc} + * + * @param T $value + * + * @return (T is null ? null : DateTimeInterface) + * + * @template T + */ + public function convertToPHPValue($value, AbstractPlatform $platform): ?\DateTimeInterface { - if (!self::$utc_timezone) { + if (!self::$utc_timezone instanceof \DateTimeZone) { self::$utc_timezone = new DateTimeZone('UTC'); } diff --git a/src/Entity/Attachments/Attachment.php b/src/Entity/Attachments/Attachment.php index 35c07571..eb931609 100644 --- a/src/Entity/Attachments/Attachment.php +++ b/src/Entity/Attachments/Attachment.php @@ -22,6 +22,9 @@ declare(strict_types=1); namespace App\Entity\Attachments; +use App\Repository\AttachmentRepository; +use App\EntityListeners\AttachmentDeleteListener; +use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractNamedDBElement; use App\Validator\Constraints\Selectable; use Doctrine\ORM\Mapping as ORM; @@ -33,27 +36,19 @@ use LogicException; /** * Class Attachment. - * - * @ORM\Entity(repositoryClass="App\Repository\AttachmentRepository") - * @ORM\Table(name="`attachments`", indexes={ - * @ORM\Index(name="attachments_idx_id_element_id_class_name", columns={"id", "element_id", "class_name"}), - * @ORM\Index(name="attachments_idx_class_name_id", columns={"class_name", "id"}), - * @ORM\Index(name="attachment_name_idx", columns={"name"}), - * @ORM\Index(name="attachment_element_idx", columns={"class_name", "element_id"}) - * }) - * @ORM\InheritanceType("SINGLE_TABLE") - * @ORM\DiscriminatorColumn(name="class_name", type="string") - * @ORM\DiscriminatorMap({ - * "PartDB\Part" = "PartAttachment", "Part" = "PartAttachment", - * "PartDB\Device" = "ProjectAttachment", "Device" = "ProjectAttachment", - * "AttachmentType" = "AttachmentTypeAttachment", "Category" = "CategoryAttachment", - * "Footprint" = "FootprintAttachment", "Manufacturer" = "ManufacturerAttachment", - * "Currency" = "CurrencyAttachment", "Group" = "GroupAttachment", - * "MeasurementUnit" = "MeasurementUnitAttachment", "Storelocation" = "StorelocationAttachment", - * "Supplier" = "SupplierAttachment", "User" = "UserAttachment", "LabelProfile" = "LabelAttachment", - * }) - * @ORM\EntityListeners({"App\EntityListeners\AttachmentDeleteListener"}) + * @see \App\Tests\Entity\Attachments\AttachmentTest + * @template-covariant T of AttachmentContainingDBElement */ +#[ORM\Entity(repositoryClass: AttachmentRepository::class)] +#[ORM\InheritanceType('SINGLE_TABLE')] +#[ORM\DiscriminatorColumn(name: 'class_name', type: 'string')] +#[ORM\DiscriminatorMap(['PartDB\Part' => 'PartAttachment', 'Part' => 'PartAttachment', 'PartDB\Device' => 'ProjectAttachment', 'Device' => 'ProjectAttachment', 'AttachmentType' => 'AttachmentTypeAttachment', 'Category' => 'CategoryAttachment', 'Footprint' => 'FootprintAttachment', 'Manufacturer' => 'ManufacturerAttachment', 'Currency' => 'CurrencyAttachment', 'Group' => 'GroupAttachment', 'MeasurementUnit' => 'MeasurementUnitAttachment', 'Storelocation' => 'StorelocationAttachment', 'Supplier' => 'SupplierAttachment', 'User' => 'UserAttachment', 'LabelProfile' => 'LabelAttachment'])] +#[ORM\EntityListeners([AttachmentDeleteListener::class])] +#[ORM\Table(name: '`attachments`')] +#[ORM\Index(name: 'attachments_idx_id_element_id_class_name', columns: ['id', 'element_id', 'class_name'])] +#[ORM\Index(name: 'attachments_idx_class_name_id', columns: ['class_name', 'id'])] +#[ORM\Index(name: 'attachment_name_idx', columns: ['name'])] +#[ORM\Index(name: 'attachment_element_idx', columns: ['class_name', 'element_id'])] abstract class Attachment extends AbstractNamedDBElement { /** @@ -61,73 +56,69 @@ abstract class Attachment extends AbstractNamedDBElement * Based on: https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types * It will be used to determine if an attachment is a picture and therefore will be shown to user as preview. */ - public const PICTURE_EXTS = ['apng', 'bmp', 'gif', 'ico', 'cur', 'jpg', 'jpeg', 'jfif', 'pjpeg', 'pjp', 'png', + final public const PICTURE_EXTS = ['apng', 'bmp', 'gif', 'ico', 'cur', 'jpg', 'jpeg', 'jfif', 'pjpeg', 'pjp', 'png', 'svg', 'webp', ]; /** * A list of extensions that will be treated as a 3D Model that can be shown to user directly in Part-DB. */ - public const MODEL_EXTS = ['x3d']; + final public const MODEL_EXTS = ['x3d']; /** * When the path begins with one of the placeholders. */ - public const INTERNAL_PLACEHOLDER = ['%BASE%', '%MEDIA%', '%SECURE%']; + final public const INTERNAL_PLACEHOLDER = ['%BASE%', '%MEDIA%', '%SECURE%']; /** * @var array placeholders for attachments which using built in files */ - public const BUILTIN_PLACEHOLDER = ['%FOOTPRINTS%', '%FOOTPRINTS3D%']; + final public const BUILTIN_PLACEHOLDER = ['%FOOTPRINTS%', '%FOOTPRINTS3D%']; /** * @var string The class of the element that can be passed to this attachment. Must be overridden in subclasses. + * @phpstan-var class-string */ - public const ALLOWED_ELEMENT_CLASS = ''; + protected const ALLOWED_ELEMENT_CLASS = AttachmentContainingDBElement::class; /** * @var string|null the original filename the file had, when the user uploaded it - * @ORM\Column(type="string", nullable=true) */ + #[ORM\Column(type: Types::STRING, nullable: true)] protected ?string $original_filename = null; /** * @var string The path to the file relative to a placeholder path like %MEDIA% - * @ORM\Column(type="string", name="path") */ + #[ORM\Column(type: Types::STRING, name: 'path')] protected string $path = ''; /** * @var string the name of this element - * @ORM\Column(type="string") - * @Assert\NotBlank(message="validator.attachment.name_not_blank") - * @Groups({"simple", "extended", "full"}) */ + #[Assert\NotBlank(message: 'validator.attachment.name_not_blank')] + #[Groups(['simple', 'extended', 'full'])] + #[ORM\Column(type: Types::STRING)] protected string $name = ''; /** * ORM mapping is done in subclasses (like PartAttachment). + * @phpstan-param T|null $element */ protected ?AttachmentContainingDBElement $element = null; - /** - * @var bool - * @ORM\Column(type="boolean") - */ + #[ORM\Column(type: Types::BOOLEAN)] protected bool $show_in_table = false; - /** - * @var AttachmentType|null - * @ORM\ManyToOne(targetEntity="AttachmentType", inversedBy="attachments_with_type") - * @ORM\JoinColumn(name="type_id", referencedColumnName="id", nullable=false) - * @Selectable() - * @Assert\NotNull(message="validator.attachment.must_not_be_null") - */ + #[Assert\NotNull(message: 'validator.attachment.must_not_be_null')] + #[ORM\ManyToOne(targetEntity: AttachmentType::class, inversedBy: 'attachments_with_type')] + #[ORM\JoinColumn(name: 'type_id', nullable: false)] + #[Selectable()] protected ?AttachmentType $attachment_type = null; public function __construct() { //parent::__construct(); - if ('' === static::ALLOWED_ELEMENT_CLASS) { + if (AttachmentContainingDBElement::class === static::ALLOWED_ELEMENT_CLASS) { throw new LogicException('An *Attachment class must override the ALLOWED_ELEMENT_CLASS const!'); } } @@ -187,7 +178,7 @@ abstract class Attachment extends AbstractNamedDBElement public function isExternal(): bool { //When path is empty, this attachment can not be external - if (empty($this->path)) { + if ($this->path === '') { return false; } @@ -241,7 +232,7 @@ abstract class Attachment extends AbstractNamedDBElement return null; } - if (!empty($this->original_filename)) { + if ($this->original_filename !== null && $this->original_filename !== '') { return strtolower(pathinfo($this->original_filename, PATHINFO_EXTENSION)); } @@ -251,7 +242,8 @@ abstract class Attachment extends AbstractNamedDBElement /** * Get the element, associated with this Attachment (for example a "Part" object). * - * @return AttachmentContainingDBElement the associated Element + * @return AttachmentContainingDBElement|null the associated Element + * @phpstan-return T|null */ public function getElement(): ?AttachmentContainingDBElement { @@ -307,7 +299,7 @@ abstract class Attachment extends AbstractNamedDBElement } //If we have a stored original filename, then use it - if (!empty($this->original_filename)) { + if ($this->original_filename !== null && $this->original_filename !== '') { return $this->original_filename; } @@ -369,7 +361,6 @@ abstract class Attachment extends AbstractNamedDBElement /** * Sets the element that is associated with this attachment. - * * @return $this */ public function setElement(AttachmentContainingDBElement $element): self @@ -416,8 +407,8 @@ abstract class Attachment extends AbstractNamedDBElement public function setURL(?string $url): self { //Only set if the URL is not empty - if (!empty($url)) { - if (false !== strpos($url, '%BASE%') || false !== strpos($url, '%MEDIA%')) { + if ($url !== null && $url !== '') { + if (str_contains($url, '%BASE%') || str_contains($url, '%MEDIA%')) { throw new InvalidArgumentException('You can not reference internal files via the url field! But nice try!'); } @@ -446,7 +437,7 @@ abstract class Attachment extends AbstractNamedDBElement $tmp = explode('/', $path); //Builtins must have a %PLACEHOLDER% construction - return in_array($tmp[0], static::BUILTIN_PLACEHOLDER, false); + return in_array($tmp[0], static::BUILTIN_PLACEHOLDER, true); } /** @@ -473,4 +464,13 @@ abstract class Attachment extends AbstractNamedDBElement return (bool) filter_var($string, FILTER_VALIDATE_URL); } + + /** + * Returns the class of the element that is allowed to be associated with this attachment. + * @return string + */ + public function getElementClass(): string + { + return static::ALLOWED_ELEMENT_CLASS; + } } diff --git a/src/Entity/Attachments/AttachmentContainingDBElement.php b/src/Entity/Attachments/AttachmentContainingDBElement.php index e74d7cb6..34f5797e 100644 --- a/src/Entity/Attachments/AttachmentContainingDBElement.php +++ b/src/Entity/Attachments/AttachmentContainingDBElement.php @@ -32,20 +32,19 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; /** - * @ORM\MappedSuperclass() + * @template-covariant AT of Attachment */ +#[ORM\MappedSuperclass] abstract class AttachmentContainingDBElement extends AbstractNamedDBElement implements HasMasterAttachmentInterface, HasAttachmentsInterface { use MasterAttachmentTrait; /** - * @var Attachment[]|Collection - * //TODO - * //@ORM\OneToMany(targetEntity="Attachment", mappedBy="element") - * - * Mapping is done in sub classes like part - * @Groups({"full"}) + * @var Collection + * @phpstan-var Collection + * ORM Mapping is done in subclasses (e.g. Part) */ + #[Groups(['full'])] protected Collection $attachments; public function __construct() @@ -79,9 +78,7 @@ abstract class AttachmentContainingDBElement extends AbstractNamedDBElement impl *********************************************************************************/ /** - * Gets all attachments associated with this element. - * - * @return Attachment[]|Collection + * Gets all attachments associated with this element. */ public function getAttachments(): Collection { diff --git a/src/Entity/Attachments/AttachmentType.php b/src/Entity/Attachments/AttachmentType.php index 683c7be5..c99ac392 100644 --- a/src/Entity/Attachments/AttachmentType.php +++ b/src/Entity/Attachments/AttachmentType.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\Attachments; +use App\Repository\StructuralDBElementRepository; +use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Parameters\AttachmentTypeParameter; use App\Validator\Constraints\ValidFileFilter; @@ -32,56 +34,55 @@ use Symfony\Component\Validator\Constraints as Assert; /** * Class AttachmentType. - * - * @ORM\Entity(repositoryClass="App\Repository\StructuralDBElementRepository") - * @ORM\Table(name="`attachment_types`", indexes={ - * @ORM\Index(name="attachment_types_idx_name", columns={"name"}), - * @ORM\Index(name="attachment_types_idx_parent_name", columns={"parent_id", "name"}), - * }) + * @see \App\Tests\Entity\Attachments\AttachmentTypeTest + * @extends AbstractStructuralDBElement */ +#[ORM\Entity(repositoryClass: StructuralDBElementRepository::class)] +#[ORM\Table(name: '`attachment_types`')] +#[ORM\Index(name: 'attachment_types_idx_name', columns: ['name'])] +#[ORM\Index(name: 'attachment_types_idx_parent_name', columns: ['parent_id', 'name'])] class AttachmentType extends AbstractStructuralDBElement { - /** - * @ORM\OneToMany(targetEntity="AttachmentType", mappedBy="parent", cascade={"persist"}) - * @ORM\OrderBy({"name" = "ASC"}) - */ + #[ORM\OneToMany(targetEntity: AttachmentType::class, mappedBy: 'parent', cascade: ['persist'])] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $children; - /** - * @ORM\ManyToOne(targetEntity="AttachmentType", inversedBy="children") - * @ORM\JoinColumn(name="parent_id", referencedColumnName="id") - */ - protected ?AbstractStructuralDBElement $parent; + #[ORM\ManyToOne(targetEntity: AttachmentType::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'parent_id')] + protected ?AbstractStructuralDBElement $parent = null; + + #[ORM\Column(type: Types::TEXT)] + #[ValidFileFilter] + protected string $filetype_filter = ''; - /** - * @var string - * @ORM\Column(type="text") - * @ValidFileFilter - */ - protected string $filetype_filter = ''; /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\AttachmentTypeAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: AttachmentTypeAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; /** @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Parameters\AttachmentTypeParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: AttachmentTypeParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; /** - * @var Collection - * @ORM\OneToMany(targetEntity="Attachment", mappedBy="attachment_type") + * @var Collection */ - protected $attachments_with_type; + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: Attachment::class, mappedBy: 'attachment_type')] + protected Collection $attachments_with_type; public function __construct() { + $this->children = new ArrayCollection(); + $this->parameters = new ArrayCollection(); parent::__construct(); $this->attachments = new ArrayCollection(); $this->attachments_with_type = new ArrayCollection(); @@ -90,8 +91,9 @@ class AttachmentType extends AbstractStructuralDBElement /** * Get all attachments ("Attachment" objects) with this type. * - * @return Collection|Attachment[] all attachments with this type, as a one-dimensional array of Attachments + * @return Collection all attachments with this type, as a one-dimensional array of Attachments * (sorted by their names) + * @phpstan-return Collection */ public function getAttachmentsForType(): Collection { diff --git a/src/Entity/Attachments/AttachmentTypeAttachment.php b/src/Entity/Attachments/AttachmentTypeAttachment.php index 8cc3e5d6..31398595 100644 --- a/src/Entity/Attachments/AttachmentTypeAttachment.php +++ b/src/Entity/Attachments/AttachmentTypeAttachment.php @@ -27,17 +27,17 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * A attachment attached to an attachmentType element. - * - * @ORM\Entity() - * @UniqueEntity({"name", "attachment_type", "element"}) + * @extends Attachment */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] class AttachmentTypeAttachment extends Attachment { - public const ALLOWED_ELEMENT_CLASS = AttachmentType::class; + final public const ALLOWED_ELEMENT_CLASS = AttachmentType::class; /** * @var AttachmentContainingDBElement|null the element this attachment is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Attachments\AttachmentType", inversedBy="attachments") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: AttachmentType::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/CategoryAttachment.php b/src/Entity/Attachments/CategoryAttachment.php index bfc54af0..63b4178d 100644 --- a/src/Entity/Attachments/CategoryAttachment.php +++ b/src/Entity/Attachments/CategoryAttachment.php @@ -28,17 +28,17 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * An attachment attached to a category element. - * - * @ORM\Entity() - * @UniqueEntity({"name", "attachment_type", "element"}) + * @extends Attachment */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] class CategoryAttachment extends Attachment { - public const ALLOWED_ELEMENT_CLASS = Category::class; + final public const ALLOWED_ELEMENT_CLASS = Category::class; /** * @var AttachmentContainingDBElement|null the element this attachment is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Category", inversedBy="attachments") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Category::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/CurrencyAttachment.php b/src/Entity/Attachments/CurrencyAttachment.php index ebfef170..292da013 100644 --- a/src/Entity/Attachments/CurrencyAttachment.php +++ b/src/Entity/Attachments/CurrencyAttachment.php @@ -28,17 +28,18 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * An attachment attached to a currency element. - * - * @ORM\Entity() - * @UniqueEntity({"name", "attachment_type", "element"}) + * @extends Attachment */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] class CurrencyAttachment extends Attachment { - public const ALLOWED_ELEMENT_CLASS = Currency::class; + final public const ALLOWED_ELEMENT_CLASS = Currency::class; + /** * @var Currency|null the element this attachment is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency", inversedBy="attachments") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Currency::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/FootprintAttachment.php b/src/Entity/Attachments/FootprintAttachment.php index 7eb65db1..4d9b7caa 100644 --- a/src/Entity/Attachments/FootprintAttachment.php +++ b/src/Entity/Attachments/FootprintAttachment.php @@ -28,17 +28,18 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * An attachment attached to a footprint element. - * - * @ORM\Entity() - * @UniqueEntity({"name", "attachment_type", "element"}) + * @extends Attachment */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] class FootprintAttachment extends Attachment { - public const ALLOWED_ELEMENT_CLASS = Footprint::class; + final public const ALLOWED_ELEMENT_CLASS = Footprint::class; + /** * @var Footprint|null the element this attachment is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Footprint", inversedBy="attachments") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Footprint::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/GroupAttachment.php b/src/Entity/Attachments/GroupAttachment.php index b6ab93f4..269e5b7d 100644 --- a/src/Entity/Attachments/GroupAttachment.php +++ b/src/Entity/Attachments/GroupAttachment.php @@ -28,18 +28,18 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * An attachment attached to a Group element. - * - * @ORM\Entity() - * @UniqueEntity({"name", "attachment_type", "element"}) + * @extends Attachment */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] class GroupAttachment extends Attachment { - public const ALLOWED_ELEMENT_CLASS = Group::class; + final public const ALLOWED_ELEMENT_CLASS = Group::class; /** * @var Group|null the element this attachment is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\Group", inversedBy="attachments") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Group::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/LabelAttachment.php b/src/Entity/Attachments/LabelAttachment.php index f0f70b79..5bf18969 100644 --- a/src/Entity/Attachments/LabelAttachment.php +++ b/src/Entity/Attachments/LabelAttachment.php @@ -47,18 +47,18 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * A attachment attached to a user element. - * - * @ORM\Entity() - * @UniqueEntity({"name", "attachment_type", "element"}) + * @extends Attachment */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] class LabelAttachment extends Attachment { - public const ALLOWED_ELEMENT_CLASS = LabelProfile::class; + final public const ALLOWED_ELEMENT_CLASS = LabelProfile::class; /** * @var LabelProfile the element this attachment is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\LabelSystem\LabelProfile", inversedBy="attachments") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: LabelProfile::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/ManufacturerAttachment.php b/src/Entity/Attachments/ManufacturerAttachment.php index 25451b7c..cdbd7807 100644 --- a/src/Entity/Attachments/ManufacturerAttachment.php +++ b/src/Entity/Attachments/ManufacturerAttachment.php @@ -28,18 +28,18 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * An attachment attached to a manufacturer element. - * - * @ORM\Entity() - * @UniqueEntity({"name", "attachment_type", "element"}) + * @extends Attachment */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] class ManufacturerAttachment extends Attachment { - public const ALLOWED_ELEMENT_CLASS = Manufacturer::class; + final public const ALLOWED_ELEMENT_CLASS = Manufacturer::class; /** * @var Manufacturer|null the element this attachment is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Manufacturer", inversedBy="attachments") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Manufacturer::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/MeasurementUnitAttachment.php b/src/Entity/Attachments/MeasurementUnitAttachment.php index 8a007101..58467f86 100644 --- a/src/Entity/Attachments/MeasurementUnitAttachment.php +++ b/src/Entity/Attachments/MeasurementUnitAttachment.php @@ -29,17 +29,17 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * An attachment attached to a measurement unit element. - * - * @ORM\Entity() - * @UniqueEntity({"name", "attachment_type", "element"}) + * @extends Attachment */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] class MeasurementUnitAttachment extends Attachment { - public const ALLOWED_ELEMENT_CLASS = MeasurementUnit::class; + final public const ALLOWED_ELEMENT_CLASS = MeasurementUnit::class; /** * @var Manufacturer|null the element this attachment is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\MeasurementUnit", inversedBy="attachments") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: MeasurementUnit::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/PartAttachment.php b/src/Entity/Attachments/PartAttachment.php index 3aa2d05c..f9ca43b3 100644 --- a/src/Entity/Attachments/PartAttachment.php +++ b/src/Entity/Attachments/PartAttachment.php @@ -28,17 +28,17 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * A attachment attached to a part element. - * - * @ORM\Entity() - * @UniqueEntity({"name", "attachment_type", "element"}) + * @extends Attachment */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] class PartAttachment extends Attachment { - public const ALLOWED_ELEMENT_CLASS = Part::class; + final public const ALLOWED_ELEMENT_CLASS = Part::class; /** * @var Part the element this attachment is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Part", inversedBy="attachments") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/ProjectAttachment.php b/src/Entity/Attachments/ProjectAttachment.php index 3f2d36d9..84b0ac4c 100644 --- a/src/Entity/Attachments/ProjectAttachment.php +++ b/src/Entity/Attachments/ProjectAttachment.php @@ -28,17 +28,17 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * A attachment attached to a device element. - * - * @ORM\Entity() - * @UniqueEntity({"name", "attachment_type", "element"}) + * @extends Attachment */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] class ProjectAttachment extends Attachment { - public const ALLOWED_ELEMENT_CLASS = Project::class; + final public const ALLOWED_ELEMENT_CLASS = Project::class; /** * @var Project|null the element this attachment is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\ProjectSystem\Project", inversedBy="attachments") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Project::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/StorelocationAttachment.php b/src/Entity/Attachments/StorelocationAttachment.php index 71e5672e..7772269d 100644 --- a/src/Entity/Attachments/StorelocationAttachment.php +++ b/src/Entity/Attachments/StorelocationAttachment.php @@ -28,18 +28,18 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * An attachment attached to a measurement unit element. - * - * @ORM\Entity() - * @UniqueEntity({"name", "attachment_type", "element"}) + * @extends Attachment */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] class StorelocationAttachment extends Attachment { - public const ALLOWED_ELEMENT_CLASS = Storelocation::class; + final public const ALLOWED_ELEMENT_CLASS = Storelocation::class; /** * @var Storelocation|null the element this attachment is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Storelocation", inversedBy="attachments") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Storelocation::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/SupplierAttachment.php b/src/Entity/Attachments/SupplierAttachment.php index a6058ccc..7afa8282 100644 --- a/src/Entity/Attachments/SupplierAttachment.php +++ b/src/Entity/Attachments/SupplierAttachment.php @@ -28,18 +28,18 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * A attachment attached to a supplier element. - * - * @ORM\Entity() - * @UniqueEntity({"name", "attachment_type", "element"}) + * @extends Attachment */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] class SupplierAttachment extends Attachment { - public const ALLOWED_ELEMENT_CLASS = Supplier::class; + final public const ALLOWED_ELEMENT_CLASS = Supplier::class; /** * @var Supplier|null the element this attachment is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Supplier", inversedBy="attachments") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Supplier::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Attachments/UserAttachment.php b/src/Entity/Attachments/UserAttachment.php index 65cd8730..fef36eb5 100644 --- a/src/Entity/Attachments/UserAttachment.php +++ b/src/Entity/Attachments/UserAttachment.php @@ -28,18 +28,18 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * An attachment attached to a user element. - * - * @ORM\Entity() - * @UniqueEntity({"name", "attachment_type", "element"}) + * @extends Attachment */ +#[UniqueEntity(['name', 'attachment_type', 'element'])] +#[ORM\Entity] class UserAttachment extends Attachment { - public const ALLOWED_ELEMENT_CLASS = User::class; + final public const ALLOWED_ELEMENT_CLASS = User::class; /** * @var User|null the element this attachment is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User", inversedBy="attachments") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'attachments')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AttachmentContainingDBElement $element = null; } diff --git a/src/Entity/Base/AbstractCompany.php b/src/Entity/Base/AbstractCompany.php index af77106f..ca6d1b88 100644 --- a/src/Entity/Base/AbstractCompany.php +++ b/src/Entity/Base/AbstractCompany.php @@ -22,6 +22,9 @@ declare(strict_types=1); namespace App\Entity\Base; +use App\Entity\Attachments\Attachment; +use App\Entity\Parameters\AbstractParameter; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; use function is_string; @@ -30,51 +33,54 @@ use Symfony\Component\Validator\Constraints as Assert; /** * This abstract class is used for companies like suppliers or manufacturers. * - * @ORM\MappedSuperclass() + * @template-covariant AT of Attachment + * @template-covariant PT of AbstractParameter + * @extends AbstractPartsContainingDBElement */ +#[ORM\MappedSuperclass] abstract class AbstractCompany extends AbstractPartsContainingDBElement { /** * @var string The address of the company - * @ORM\Column(type="string") - * @Groups({"full"}) */ + #[Groups(['full'])] + #[ORM\Column(type: Types::STRING)] protected string $address = ''; /** * @var string The phone number of the company - * @ORM\Column(type="string") - * @Groups({"full"}) */ + #[Groups(['full'])] + #[ORM\Column(type: Types::STRING)] protected string $phone_number = ''; /** * @var string The fax number of the company - * @ORM\Column(type="string") - * @Groups({"full"}) */ + #[Groups(['full'])] + #[ORM\Column(type: Types::STRING)] protected string $fax_number = ''; /** * @var string The email address of the company - * @ORM\Column(type="string") - * @Assert\Email() - * @Groups({"full"}) */ + #[Assert\Email] + #[Groups(['full'])] + #[ORM\Column(type: Types::STRING)] protected string $email_address = ''; /** * @var string The website of the company - * @ORM\Column(type="string") - * @Assert\Url() - * @Groups({"full"}) */ + #[Assert\Url] + #[Groups(['full'])] + #[ORM\Column(type: Types::STRING)] protected string $website = ''; /** * @var string - * @ORM\Column(type="string") */ + #[ORM\Column(type: Types::STRING)] protected string $auto_product_url = ''; /******************************************************************************** diff --git a/src/Entity/Base/AbstractDBElement.php b/src/Entity/Base/AbstractDBElement.php index 97b77d53..30fcab06 100644 --- a/src/Entity/Base/AbstractDBElement.php +++ b/src/Entity/Base/AbstractDBElement.php @@ -22,6 +22,37 @@ declare(strict_types=1); namespace App\Entity\Base; +use App\Entity\Attachments\AttachmentType; +use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\AttachmentTypeAttachment; +use App\Entity\Attachments\CategoryAttachment; +use App\Entity\Attachments\CurrencyAttachment; +use App\Entity\Attachments\FootprintAttachment; +use App\Entity\Attachments\GroupAttachment; +use App\Entity\Attachments\LabelAttachment; +use App\Entity\Attachments\ManufacturerAttachment; +use App\Entity\Attachments\MeasurementUnitAttachment; +use App\Entity\Attachments\PartAttachment; +use App\Entity\Attachments\ProjectAttachment; +use App\Entity\Attachments\StorelocationAttachment; +use App\Entity\Attachments\SupplierAttachment; +use App\Entity\Attachments\UserAttachment; +use App\Entity\Parts\Category; +use App\Entity\ProjectSystem\Project; +use App\Entity\ProjectSystem\ProjectBOMEntry; +use App\Entity\Parts\Footprint; +use App\Entity\UserSystem\Group; +use App\Entity\Parts\Manufacturer; +use App\Entity\PriceInformations\Orderdetail; +use App\Entity\Parts\Part; +use App\Entity\Parts\Storelocation; +use App\Entity\Parts\PartLot; +use App\Entity\PriceInformations\Currency; +use App\Entity\Parts\MeasurementUnit; +use App\Entity\Parts\Supplier; +use App\Entity\UserSystem\User; +use App\Repository\DBElementRepository; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use JsonSerializable; use Symfony\Component\Serializer\Annotation\DiscriminatorMap; @@ -34,52 +65,18 @@ use Symfony\Component\Serializer\Annotation\Groups; * (except special tables like "internal"...) * Every database table which are managed with this class (or a subclass of it) * must have the table row "id"!! The ID is the unique key to identify the elements. - * - * @ORM\MappedSuperclass(repositoryClass="App\Repository\DBElementRepository") - * - * @DiscriminatorMap(typeProperty="type", mapping={ - * "attachment_type" = "App\Entity\Attachments\AttachmentType", - * "attachment" = "App\Entity\Attachments\Attachment", - * "attachment_type_attachment" = "App\Entity\Attachments\AttachmentTypeAttachment", - * "category_attachment" = "App\Entity\Attachments\CategoryAttachment", - * "currency_attachment" = "App\Entity\Attachments\CurrencyAttachment", - * "footprint_attachment" = "App\Entity\Attachments\FootprintAttachment", - * "group_attachment" = "App\Entity\Attachments\GroupAttachment", - * "label_attachment" = "App\Entity\Attachments\LabelAttachment", - * "manufacturer_attachment" = "App\Entity\Attachments\ManufacturerAttachment", - * "measurement_unit_attachment" = "App\Entity\Attachments\MeasurementUnitAttachment", - * "part_attachment" = "App\Entity\Attachments\PartAttachment", - * "project_attachment" = "App\Entity\Attachments\ProjectAttachment", - * "storelocation_attachment" = "App\Entity\Attachments\StorelocationAttachment", - * "supplier_attachment" = "App\Entity\Attachments\SupplierAttachment", - * "user_attachment" = "App\Entity\Attachments\UserAttachment", - * "category" = "App\Entity\Parts\Category", - * "project" = "App\Entity\ProjectSystem\Project", - * "project_bom_entry" = "App\Entity\ProjectSystem\ProjectBOMEntry", - * "footprint" = "App\Entity\Parts\Footprint", - * "group" = "App\Entity\UserSystem\Group", - * "manufacturer" = "App\Entity\Parts\Manufacturer", - * "orderdetail" = "App\Entity\PriceInformations\Orderdetail", - * "part" = "App\Entity\Parts\Part", - * "pricedetail" = "App\Entity\PriceInformation\Pricedetail", - * "storelocation" = "App\Entity\Parts\Storelocation", - * "part_lot" = "App\Entity\Parts\PartLot", - * "currency" = "App\Entity\PriceInformations\Currency", - * "measurement_unit" = "App\Entity\Parts\MeasurementUnit", - * "parameter" = "App\Entity\Parts\AbstractParameter", - * "supplier" = "App\Entity\Parts\Supplier", - * "user" = "App\Entity\UserSystem\User" - * }) */ +#[DiscriminatorMap(typeProperty: 'type', mapping: ['attachment_type' => AttachmentType::class, 'attachment' => Attachment::class, 'attachment_type_attachment' => AttachmentTypeAttachment::class, 'category_attachment' => CategoryAttachment::class, 'currency_attachment' => CurrencyAttachment::class, 'footprint_attachment' => FootprintAttachment::class, 'group_attachment' => GroupAttachment::class, 'label_attachment' => LabelAttachment::class, 'manufacturer_attachment' => ManufacturerAttachment::class, 'measurement_unit_attachment' => MeasurementUnitAttachment::class, 'part_attachment' => PartAttachment::class, 'project_attachment' => ProjectAttachment::class, 'storelocation_attachment' => StorelocationAttachment::class, 'supplier_attachment' => SupplierAttachment::class, 'user_attachment' => UserAttachment::class, 'category' => Category::class, 'project' => Project::class, 'project_bom_entry' => ProjectBOMEntry::class, 'footprint' => Footprint::class, 'group' => Group::class, 'manufacturer' => Manufacturer::class, 'orderdetail' => Orderdetail::class, 'part' => Part::class, 'pricedetail' => 'App\Entity\PriceInformation\Pricedetail', 'storelocation' => Storelocation::class, 'part_lot' => PartLot::class, 'currency' => Currency::class, 'measurement_unit' => MeasurementUnit::class, 'parameter' => 'App\Entity\Parts\AbstractParameter', 'supplier' => Supplier::class, 'user' => User::class])] +#[ORM\MappedSuperclass(repositoryClass: DBElementRepository::class)] abstract class AbstractDBElement implements JsonSerializable { /** @var int|null The Identification number for this part. This value is unique for the element in this table. * Null if the element is not saved to DB yet. - * @ORM\Column(type="integer") - * @ORM\Id() - * @ORM\GeneratedValue() - * @Groups({"full"}) */ + #[Groups(['full'])] + #[ORM\Column(type: Types::INTEGER)] + #[ORM\Id] + #[ORM\GeneratedValue] protected ?int $id = null; public function __clone() diff --git a/src/Entity/Base/AbstractNamedDBElement.php b/src/Entity/Base/AbstractNamedDBElement.php index ddb758c0..e5e30441 100644 --- a/src/Entity/Base/AbstractNamedDBElement.php +++ b/src/Entity/Base/AbstractNamedDBElement.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\Base; +use App\Repository\NamedDBElementRepository; +use Doctrine\DBAL\Types\Types; use App\Entity\Contracts\NamedElementInterface; use App\Entity\Contracts\TimeStampableInterface; use Doctrine\ORM\Mapping as ORM; @@ -30,20 +32,19 @@ use Symfony\Component\Validator\Constraints as Assert; /** * All subclasses of this class have an attribute "name". - * - * @ORM\MappedSuperclass(repositoryClass="App\Repository\NamedDBElement") - * @ORM\HasLifecycleCallbacks() */ -abstract class AbstractNamedDBElement extends AbstractDBElement implements NamedElementInterface, TimeStampableInterface +#[ORM\MappedSuperclass(repositoryClass: NamedDBElementRepository::class)] +#[ORM\HasLifecycleCallbacks] +abstract class AbstractNamedDBElement extends AbstractDBElement implements NamedElementInterface, TimeStampableInterface, \Stringable { use TimestampTrait; /** * @var string the name of this element - * @ORM\Column(type="string") - * @Assert\NotBlank() - * @Groups({"simple", "extended", "full", "import"}) */ + #[Assert\NotBlank] + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\Column(type: Types::STRING)] protected string $name = ''; /****************************************************************************** @@ -52,7 +53,7 @@ abstract class AbstractNamedDBElement extends AbstractDBElement implements Named * ******************************************************************************/ - public function __toString() + public function __toString(): string { return $this->getName(); } diff --git a/src/Entity/Base/AbstractPartsContainingDBElement.php b/src/Entity/Base/AbstractPartsContainingDBElement.php index 52326907..5d25283e 100644 --- a/src/Entity/Base/AbstractPartsContainingDBElement.php +++ b/src/Entity/Base/AbstractPartsContainingDBElement.php @@ -22,17 +22,30 @@ declare(strict_types=1); namespace App\Entity\Base; +use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\AttachmentContainingDBElement; +use App\Entity\Parameters\AbstractParameter; +use App\Entity\Parameters\ParametersTrait; +use App\Repository\AbstractPartsContainingRepository; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; /** - * Class PartsContainingDBElement. - * - * @ORM\MappedSuperclass(repositoryClass="App\Repository\AbstractPartsContainingRepository") + * @template-covariant AT of Attachment + * @template-covariant PT of AbstractParameter + * @extends AbstractStructuralDBElement */ +#[ORM\MappedSuperclass(repositoryClass: AbstractPartsContainingRepository::class)] abstract class AbstractPartsContainingDBElement extends AbstractStructuralDBElement { - /** @Groups({"full"}) */ + #[Groups(['full'])] protected Collection $parameters; + + public function __construct() + { + parent::__construct(); + $this->parameters = new ArrayCollection(); + } } diff --git a/src/Entity/Base/AbstractStructuralDBElement.php b/src/Entity/Base/AbstractStructuralDBElement.php index 1353448d..eee8ebcc 100644 --- a/src/Entity/Base/AbstractStructuralDBElement.php +++ b/src/Entity/Base/AbstractStructuralDBElement.php @@ -22,9 +22,17 @@ declare(strict_types=1); namespace App\Entity\Base; +use App\Entity\Attachments\Attachment; +use App\Entity\Parameters\AbstractParameter; +use App\Repository\StructuralDBElementRepository; +use App\EntityListeners\TreeCacheInvalidationListener; +use Doctrine\Common\Proxy\Proxy; +use Doctrine\DBAL\Types\Types; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Parameters\ParametersTrait; use App\Validator\Constraints\NoneOfItsChildren; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Constraints\Valid; use function count; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -43,36 +51,40 @@ use Symfony\Component\Serializer\Annotation\Groups; * It's allowed to have instances of root elements, but if you try to change * an attribute of a root element, you will get an exception! * - * @ORM\MappedSuperclass(repositoryClass="App\Repository\StructuralDBElementRepository") * - * @ORM\EntityListeners({"App\EntityListeners\TreeCacheInvalidationListener"}) + * @see \App\Tests\Entity\Base\AbstractStructuralDBElementTest * - * @UniqueEntity(fields={"name", "parent"}, ignoreNull=false, message="structural.entity.unique_name") + * @template-covariant AT of Attachment + * @template-covariant PT of AbstractParameter + * @template-use ParametersTrait + * @extends AttachmentContainingDBElement + * @uses ParametersTrait */ +#[UniqueEntity(fields: ['name', 'parent'], ignoreNull: false, message: 'structural.entity.unique_name')] +#[ORM\MappedSuperclass(repositoryClass: StructuralDBElementRepository::class)] +#[ORM\EntityListeners([TreeCacheInvalidationListener::class])] abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement { use ParametersTrait; - public const ID_ROOT_ELEMENT = 0; - /** * This is a not standard character, so build a const, so a dev can easily use it. */ - public const PATH_DELIMITER_ARROW = ' → '; + final public const PATH_DELIMITER_ARROW = ' → '; /** * @var string The comment info for this element - * @ORM\Column(type="text") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::TEXT)] protected string $comment = ''; /** * @var bool If this property is set, this element can not be selected for part properties. * Useful if this element should be used only for grouping, sorting. - * @ORM\Column(type="boolean") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $not_selectable = false; /** @@ -84,18 +96,29 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement * We can not define the mapping here, or we will get an exception. Unfortunately we have to do the mapping in the * subclasses. * - * @var AbstractStructuralDBElement[]|Collection - * @Groups({"include_children"}) + * @var Collection + * @phpstan-var Collection */ + #[Groups(['include_children'])] protected Collection $children; /** - * @var AbstractStructuralDBElement - * @NoneOfItsChildren() - * @Groups({"include_parents", "import"}) + * @var AbstractStructuralDBElement|null + * @phpstan-var static|null */ + #[Groups(['include_parents', 'import'])] + #[NoneOfItsChildren] protected ?AbstractStructuralDBElement $parent = null; + /** + * Mapping done in subclasses. + * + * @var Collection + * @phpstan-var Collection + */ + #[Assert\Valid()] + protected Collection $parameters; + /** @var string[] all names of all parent elements as an array of strings, * the last array element is the name of the element itself */ @@ -106,7 +129,6 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement parent::__construct(); $this->children = new ArrayCollection(); $this->parameters = new ArrayCollection(); - $this->parent = null; } public function __clone() @@ -142,11 +164,11 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement //Check if both elements compared, are from the same type // (we have to check inheritance, or we get exceptions when using doctrine entities (they have a proxy type): - if (!is_a($another_element, $class_name) && !is_a($this, get_class($another_element))) { + if (!$another_element instanceof $class_name && !is_a($this, $another_element::class)) { throw new InvalidArgumentException('isChildOf() only works for objects of the same type!'); } - if (null === $this->getParent()) { // this is the root node + if (!$this->getParent() instanceof \App\Entity\Base\AbstractStructuralDBElement) { // this is the root node return false; } @@ -171,7 +193,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement */ public function isRoot(): bool { - return null === $this->parent; + return $this->parent === null; } /****************************************************************************** @@ -183,7 +205,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement /** * Get the parent of this element. * - * @return AbstractStructuralDBElement|null The parent element. Null if this element, does not have a parent. + * @return static|null The parent element. Null if this element, does not have a parent. */ public function getParent(): ?self { @@ -214,9 +236,9 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement /* * Only check for nodes that have a parent. In the other cases zero is correct. */ - if (0 === $this->level && null !== $this->parent) { + if (0 === $this->level && $this->parent instanceof \App\Entity\Base\AbstractStructuralDBElement) { $element = $this->parent; - while (null !== $element) { + while ($element instanceof \App\Entity\Base\AbstractStructuralDBElement) { /** @var AbstractStructuralDBElement $element */ $element = $element->parent; ++$this->level; @@ -235,14 +257,14 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement */ public function getFullPath(string $delimiter = self::PATH_DELIMITER_ARROW): string { - if (empty($this->full_path_strings)) { + if ($this->full_path_strings === []) { $this->full_path_strings = []; $this->full_path_strings[] = $this->getName(); $element = $this; $overflow = 20; //We only allow 20 levels depth - while (null !== $element->parent && $overflow >= 0) { + while ($element->parent instanceof \App\Entity\Base\AbstractStructuralDBElement && $overflow >= 0) { $element = $element->parent; $this->full_path_strings[] = $element->getName(); //Decrement to prevent mem overflow. @@ -315,9 +337,8 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement /** * Sets the new parent object. * - * @param AbstractStructuralDBElement|null $new_parent The new parent object - * - * @return AbstractStructuralDBElement + * @param static|null $new_parent The new parent object + * @return $this */ public function setParent(?self $new_parent): self { @@ -329,7 +350,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement $this->parent = $new_parent; //Add this element as child to the new parent - if (null !== $new_parent) { + if ($new_parent instanceof \App\Entity\Base\AbstractStructuralDBElement) { $new_parent->getChildren()->add($this); } @@ -341,7 +362,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement * * @param string $new_comment the new comment * - * @return AbstractStructuralDBElement + * @return $this */ public function setComment(string $new_comment): self { diff --git a/src/Entity/Base/MasterAttachmentTrait.php b/src/Entity/Base/MasterAttachmentTrait.php index b6c5657f..21168282 100644 --- a/src/Entity/Base/MasterAttachmentTrait.php +++ b/src/Entity/Base/MasterAttachmentTrait.php @@ -33,10 +33,10 @@ trait MasterAttachmentTrait { /** * @var Attachment|null - * @ORM\ManyToOne(targetEntity="App\Entity\Attachments\Attachment") - * @ORM\JoinColumn(name="id_preview_attachment", referencedColumnName="id", onDelete="SET NULL", nullable=true) - * @Assert\Expression("value == null or value.isPicture()", message="part.master_attachment.must_be_picture") */ + #[Assert\Expression('value == null or value.isPicture()', message: 'part.master_attachment.must_be_picture')] + #[ORM\ManyToOne(targetEntity: Attachment::class)] + #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] protected ?Attachment $master_picture_attachment = null; /** diff --git a/src/Entity/Base/PartsContainingRepositoryInterface.php b/src/Entity/Base/PartsContainingRepositoryInterface.php index 16932677..f852bc35 100644 --- a/src/Entity/Base/PartsContainingRepositoryInterface.php +++ b/src/Entity/Base/PartsContainingRepositoryInterface.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\Base; use App\Entity\Parts\Part; diff --git a/src/Entity/Base/TimestampTrait.php b/src/Entity/Base/TimestampTrait.php index df3779eb..93e58cb7 100644 --- a/src/Entity/Base/TimestampTrait.php +++ b/src/Entity/Base/TimestampTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Base; +use Doctrine\DBAL\Types\Types; use DateTime; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; @@ -32,26 +33,26 @@ use Symfony\Component\Serializer\Annotation\Groups; trait TimestampTrait { /** - * @var DateTime|null the date when this element was modified the last time - * @ORM\Column(type="datetime", name="last_modified", options={"default":"CURRENT_TIMESTAMP"}) - * @Groups({"extended", "full"}) + * @var \DateTimeInterface|null the date when this element was modified the last time */ - protected ?DateTime $lastModified = null; + #[Groups(['extended', 'full'])] + #[ORM\Column(name: 'last_modified', type: Types::DATETIME_MUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] + protected ?\DateTimeInterface $lastModified = null; /** - * @var DateTime|null the date when this element was created - * @ORM\Column(type="datetime", name="datetime_added", options={"default":"CURRENT_TIMESTAMP"}) - * @Groups({"extended", "full"}) + * @var \DateTimeInterface|null the date when this element was created */ - protected ?DateTime $addedDate = null; + #[Groups(['extended', 'full'])] + #[ORM\Column(name: 'datetime_added', type: Types::DATETIME_MUTABLE, options: ['default' => 'CURRENT_TIMESTAMP'])] + protected ?\DateTimeInterface $addedDate = null; /** * Returns the last time when the element was modified. * Returns null if the element was not yet saved to DB yet. * - * @return DateTime|null the time of the last edit + * @return \DateTimeInterface|null the time of the last edit */ - public function getLastModified(): ?DateTime + public function getLastModified(): ?\DateTimeInterface { return $this->lastModified; } @@ -60,19 +61,18 @@ trait TimestampTrait * Returns the date/time when the element was created. * Returns null if the element was not yet saved to DB yet. * - * @return DateTime|null the creation time of the part + * @return \DateTimeInterface|null the creation time of the part */ - public function getAddedDate(): ?DateTime + public function getAddedDate(): ?\DateTimeInterface { return $this->addedDate; } /** * Helper for updating the timestamp. It is automatically called by doctrine before persisting. - * - * @ORM\PrePersist - * @ORM\PreUpdate */ + #[ORM\PrePersist] + #[ORM\PreUpdate] public function updateTimestamps(): void { $this->lastModified = new DateTime('now'); diff --git a/src/Entity/Contracts/LogWithEventUndoInterface.php b/src/Entity/Contracts/LogWithEventUndoInterface.php index fecc6eaa..1e8db0a1 100644 --- a/src/Entity/Contracts/LogWithEventUndoInterface.php +++ b/src/Entity/Contracts/LogWithEventUndoInterface.php @@ -42,6 +42,7 @@ declare(strict_types=1); namespace App\Entity\Contracts; use App\Entity\LogSystem\AbstractLogEntry; +use App\Services\LogSystem\EventUndoMode; interface LogWithEventUndoInterface { @@ -60,12 +61,12 @@ interface LogWithEventUndoInterface * * @return $this */ - public function setUndoneEvent(AbstractLogEntry $event, string $mode = 'undo'): self; + public function setUndoneEvent(AbstractLogEntry $event, EventUndoMode $mode = EventUndoMode::UNDO): self; /** * Returns the mode how the event was undone: * "undo" = Only a single event was applied to element * "revert" = Element was reverted to the state it was to the timestamp of the log. */ - public function getUndoMode(): string; + public function getUndoMode(): EventUndoMode; } diff --git a/src/Entity/Contracts/LogWithNewDataInterface.php b/src/Entity/Contracts/LogWithNewDataInterface.php index 0ecad0f2..c4128cb7 100644 --- a/src/Entity/Contracts/LogWithNewDataInterface.php +++ b/src/Entity/Contracts/LogWithNewDataInterface.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\Contracts; interface LogWithNewDataInterface @@ -38,4 +40,4 @@ interface LogWithNewDataInterface * @return $this */ public function setNewData(array $new_data): self; -} \ No newline at end of file +} diff --git a/src/Entity/Contracts/TimeStampableInterface.php b/src/Entity/Contracts/TimeStampableInterface.php index e198ae7c..6393d629 100644 --- a/src/Entity/Contracts/TimeStampableInterface.php +++ b/src/Entity/Contracts/TimeStampableInterface.php @@ -30,15 +30,15 @@ interface TimeStampableInterface * Returns the last time when the element was modified. * Returns null if the element was not yet saved to DB yet. * - * @return DateTime|null the time of the last edit + * @return \DateTimeInterface|null the time of the last edit */ - public function getLastModified(): ?DateTime; + public function getLastModified(): ?\DateTimeInterface; /** * Returns the date/time when the element was created. * Returns null if the element was not yet saved to DB yet. * - * @return DateTime|null the creation time of the part + * @return \DateTimeInterface|null the creation time of the part */ - public function getAddedDate(): ?DateTime; + public function getAddedDate(): ?\DateTimeInterface; } diff --git a/src/Entity/Contracts/TimeTravelInterface.php b/src/Entity/Contracts/TimeTravelInterface.php index c756f53e..064f4fec 100644 --- a/src/Entity/Contracts/TimeTravelInterface.php +++ b/src/Entity/Contracts/TimeTravelInterface.php @@ -41,5 +41,5 @@ interface TimeTravelInterface /** * Returns the timestamp associated with this change. */ - public function getTimestamp(): DateTime; + public function getTimestamp(): \DateTimeInterface; } diff --git a/src/Entity/LabelSystem/BarcodeType.php b/src/Entity/LabelSystem/BarcodeType.php new file mode 100644 index 00000000..0794b606 --- /dev/null +++ b/src/Entity/LabelSystem/BarcodeType.php @@ -0,0 +1,64 @@ +. + */ + +namespace App\Entity\LabelSystem; + +enum BarcodeType: string +{ + case NONE = 'none'; + case QR = 'qr'; + case CODE39 = 'code39'; + case DATAMATRIX = 'datamatrix'; + case CODE93 = 'code93'; + case CODE128 = 'code128'; + + /** + * Returns true if the barcode is none. (Useful for twig templates) + * @return bool + */ + public function isNone(): bool + { + return $this === self::NONE; + } + + /** + * Returns true if the barcode is a 1D barcode (Code39, etc.). + * @return bool + */ + public function is1D(): bool + { + return match ($this) { + self::CODE39, self::CODE93, self::CODE128 => true, + default => false, + }; + } + + /** + * Returns true if the barcode is a 2D barcode (QR code, datamatrix). + * @return bool + */ + public function is2D(): bool + { + return match ($this) { + self::QR, self::DATAMATRIX => true, + default => false, + }; + } +} diff --git a/src/Entity/LabelSystem/LabelOptions.php b/src/Entity/LabelSystem/LabelOptions.php index f3f448ad..1d15d0f5 100644 --- a/src/Entity/LabelSystem/LabelOptions.php +++ b/src/Entity/LabelSystem/LabelOptions.php @@ -41,71 +41,57 @@ declare(strict_types=1); namespace App\Entity\LabelSystem; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; -/** - * @ORM\Embeddable() - */ +#[ORM\Embeddable] class LabelOptions { - public const BARCODE_TYPES = ['none', /*'ean8',*/ 'qr', 'code39', 'datamatrix', 'code93', 'code128']; - public const SUPPORTED_ELEMENTS = ['part', 'part_lot', 'storelocation']; - public const PICTURE_TYPES = ['none', 'element_picture', 'main_attachment']; - - public const LINES_MODES = ['html', 'twig']; - /** * @var float The page size of the label in mm - * @Assert\Positive() - * @ORM\Column(type="float") */ + #[Assert\Positive] + #[ORM\Column(type: Types::FLOAT)] protected float $width = 50.0; /** * @var float The page size of the label in mm - * @Assert\Positive() - * @ORM\Column(type="float") */ + #[Assert\Positive] + #[ORM\Column(type: Types::FLOAT)] protected float $height = 30.0; /** - * @var string The type of the barcode that should be used in the label (e.g. 'qr') - * @Assert\Choice(choices=LabelOptions::BARCODE_TYPES) - * @ORM\Column(type="string") + * @var BarcodeType The type of the barcode that should be used in the label (e.g. 'qr') */ - protected string $barcode_type = 'none'; + #[ORM\Column(type: Types::STRING, enumType: BarcodeType::class)] + protected BarcodeType $barcode_type = BarcodeType::NONE; /** - * @var string What image should be shown along the - * @Assert\Choice(choices=LabelOptions::PICTURE_TYPES) - * @ORM\Column(type="string") + * @var LabelPictureType What image should be shown along the label */ - protected string $picture_type = 'none'; + #[ORM\Column(type: Types::STRING, enumType: LabelPictureType::class)] + protected LabelPictureType $picture_type = LabelPictureType::NONE; - /** - * @var string - * @Assert\Choice(choices=LabelOptions::SUPPORTED_ELEMENTS) - * @ORM\Column(type="string") - */ - protected string $supported_element = 'part'; + #[ORM\Column(type: Types::STRING, enumType: LabelSupportedElement::class)] + protected LabelSupportedElement $supported_element = LabelSupportedElement::PART; /** * @var string any additional CSS for the label - * @ORM\Column(type="text") */ + #[ORM\Column(type: Types::TEXT)] protected string $additional_css = ''; - /** @var string The mode that will be used to interpret the lines - * @Assert\Choice(choices=LabelOptions::LINES_MODES) - * @ORM\Column(type="string") + /** @var LabelProcessMode The mode that will be used to interpret the lines */ - protected string $lines_mode = 'html'; + #[ORM\Column(type: Types::STRING, enumType: LabelProcessMode::class, name: 'lines_mode')] + protected LabelProcessMode $process_mode = LabelProcessMode::PLACEHOLDER; /** * @var string - * @ORM\Column(type="text") */ + #[ORM\Column(type: Types::TEXT)] protected string $lines = ''; public function getWidth(): float @@ -113,9 +99,6 @@ class LabelOptions return $this->width; } - /** - * @return LabelOptions - */ public function setWidth(float $width): self { $this->width = $width; @@ -128,9 +111,6 @@ class LabelOptions return $this->height; } - /** - * @return LabelOptions - */ public function setHeight(float $height): self { $this->height = $height; @@ -138,45 +118,36 @@ class LabelOptions return $this; } - public function getBarcodeType(): string + public function getBarcodeType(): BarcodeType { return $this->barcode_type; } - /** - * @return LabelOptions - */ - public function setBarcodeType(string $barcode_type): self + public function setBarcodeType(BarcodeType $barcode_type): self { $this->barcode_type = $barcode_type; return $this; } - public function getPictureType(): string + public function getPictureType(): LabelPictureType { return $this->picture_type; } - /** - * @return LabelOptions - */ - public function setPictureType(string $picture_type): self + public function setPictureType(LabelPictureType $picture_type): self { $this->picture_type = $picture_type; return $this; } - public function getSupportedElement(): string + public function getSupportedElement(): LabelSupportedElement { return $this->supported_element; } - /** - * @return LabelOptions - */ - public function setSupportedElement(string $supported_element): self + public function setSupportedElement(LabelSupportedElement $supported_element): self { $this->supported_element = $supported_element; @@ -188,9 +159,6 @@ class LabelOptions return $this->lines; } - /** - * @return LabelOptions - */ public function setLines(string $lines): self { $this->lines = $lines; @@ -206,9 +174,6 @@ class LabelOptions return $this->additional_css; } - /** - * @return LabelOptions - */ public function setAdditionalCss(string $additional_css): self { $this->additional_css = $additional_css; @@ -216,17 +181,14 @@ class LabelOptions return $this; } - public function getLinesMode(): string + public function getProcessMode(): LabelProcessMode { - return $this->lines_mode; + return $this->process_mode; } - /** - * @return LabelOptions - */ - public function setLinesMode(string $lines_mode): self + public function setProcessMode(LabelProcessMode $process_mode): self { - $this->lines_mode = $lines_mode; + $this->process_mode = $process_mode; return $this; } diff --git a/src/Entity/LabelSystem/LabelPictureType.php b/src/Entity/LabelSystem/LabelPictureType.php new file mode 100644 index 00000000..c9183ca6 --- /dev/null +++ b/src/Entity/LabelSystem/LabelPictureType.php @@ -0,0 +1,37 @@ +. + */ + +namespace App\Entity\LabelSystem; + +enum LabelPictureType: string +{ + /** + * Show no picture on the label + */ + case NONE = 'none'; + /** + * Show the preview picture of the element on the label + */ + case ELEMENT_PICTURE = 'element_picture'; + /** + * Show the main attachment of the element on the label + */ + case MAIN_ATTACHMENT = 'main_attachment'; +} \ No newline at end of file diff --git a/assets/css/app/darkmode.css b/src/Entity/LabelSystem/LabelProcessMode.php similarity index 63% rename from assets/css/app/darkmode.css rename to src/Entity/LabelSystem/LabelProcessMode.php index 8ef745f4..76bf175f 100644 --- a/assets/css/app/darkmode.css +++ b/src/Entity/LabelSystem/LabelProcessMode.php @@ -1,7 +1,8 @@ +. */ -.darkmode-layer { - z-index: 2020; -} +namespace App\Entity\LabelSystem; -/** If darkmode is enabled revert the blening for images and videos, as these should be shown not inverted */ -.darkmode--activated img, -.darkmode--activated video, -.darkmode--activated object { - mix-blend-mode: difference; -} - -.darkmode--activated .hoverpic:hover { - background: black; -} - -.tools-ic-logos img { - mix-blend-mode: normal; +enum LabelProcessMode: string +{ + /** Use placeholders like [[PLACEHOLDER]] which gets replaced with content */ + case PLACEHOLDER = 'html'; + /** Interpret the given lines as twig template */ + case TWIG = 'twig'; } \ No newline at end of file diff --git a/src/Entity/LabelSystem/LabelProfile.php b/src/Entity/LabelSystem/LabelProfile.php index 12f6d659..30429985 100644 --- a/src/Entity/LabelSystem/LabelProfile.php +++ b/src/Entity/LabelSystem/LabelProfile.php @@ -41,6 +41,10 @@ declare(strict_types=1); namespace App\Entity\LabelSystem; +use App\Repository\LabelProfileRepository; +use App\EntityListeners\TreeCacheInvalidationListener; +use Doctrine\DBAL\Types\Types; +use Doctrine\Common\Collections\ArrayCollection; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\LabelAttachment; use Doctrine\Common\Collections\Collection; @@ -49,41 +53,43 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Validator\Constraints as Assert; /** - * @ORM\Entity(repositoryClass="App\Repository\LabelProfileRepository") - * @ORM\Table(name="label_profiles") - * @ORM\EntityListeners({"App\EntityListeners\TreeCacheInvalidationListener"}) - * @UniqueEntity({"name", "options.supported_element"}) + * @extends AttachmentContainingDBElement */ +#[UniqueEntity(['name', 'options.supported_element'])] +#[ORM\Entity(repositoryClass: LabelProfileRepository::class)] +#[ORM\EntityListeners([TreeCacheInvalidationListener::class])] +#[ORM\Table(name: 'label_profiles')] class LabelProfile extends AttachmentContainingDBElement { /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\LabelAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"name" = "ASC"}) */ + #[ORM\OneToMany(targetEntity: LabelAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; /** * @var LabelOptions - * @ORM\Embedded(class="LabelOptions") - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\Embedded(class: 'LabelOptions')] protected LabelOptions $options; /** * @var string The comment info for this element - * @ORM\Column(type="text") */ + #[ORM\Column(type: Types::TEXT)] protected string $comment = ''; /** * @var bool determines, if this label profile should be shown in the dropdown quick menu - * @ORM\Column(type="boolean") */ + #[ORM\Column(type: Types::BOOLEAN)] protected bool $show_in_dropdown = true; public function __construct() { + $this->attachments = new ArrayCollection(); parent::__construct(); $this->options = new LabelOptions(); } @@ -127,8 +133,6 @@ class LabelProfile extends AttachmentContainingDBElement /** * Sets the show in dropdown menu. - * - * @return LabelProfile */ public function setShowInDropdown(bool $show_in_dropdown): self { diff --git a/src/Entity/LabelSystem/LabelSupportedElement.php b/src/Entity/LabelSystem/LabelSupportedElement.php new file mode 100644 index 00000000..99bac6c9 --- /dev/null +++ b/src/Entity/LabelSystem/LabelSupportedElement.php @@ -0,0 +1,45 @@ +. + */ + +namespace App\Entity\LabelSystem; + +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use App\Entity\Parts\Storelocation; + +enum LabelSupportedElement: string +{ + case PART = 'part'; + case PART_LOT = 'part_lot'; + case STORELOCATION = 'storelocation'; + + /** + * Returns the entity class for the given element type + * @return string + */ + public function getEntityClass(): string + { + return match ($this) { + self::PART => Part::class, + self::PART_LOT => PartLot::class, + self::STORELOCATION => Storelocation::class, + }; + } +} \ No newline at end of file diff --git a/src/Entity/LogSystem/AbstractLogEntry.php b/src/Entity/LogSystem/AbstractLogEntry.php index 703049e8..1041cd6d 100644 --- a/src/Entity/LogSystem/AbstractLogEntry.php +++ b/src/Entity/LogSystem/AbstractLogEntry.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\LogSystem; +use Doctrine\DBAL\Types\Types; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractDBElement; @@ -45,134 +46,55 @@ use App\Entity\UserSystem\User; use DateTime; use Doctrine\ORM\Mapping as ORM; use InvalidArgumentException; -use Psr\Log\LogLevel; +use Psr\Log\LogLevel as PsrLogLevel; +use App\Repository\LogEntryRepository; /** * This entity describes an entry in the event log. - * - * @ORM\Entity(repositoryClass="App\Repository\LogEntryRepository") - * @ORM\Table("log", indexes={ - * @ORM\Index(name="log_idx_type", columns={"type"}), - * @ORM\Index(name="log_idx_type_target", columns={"type", "target_type", "target_id"}), - * @ORM\Index(name="log_idx_datetime", columns={"datetime"}), - * }) - * @ORM\InheritanceType("SINGLE_TABLE") - * @ORM\DiscriminatorColumn(name="type", type="smallint") - * @ORM\DiscriminatorMap({ - * 1 = "UserLoginLogEntry", - * 2 = "UserLogoutLogEntry", - * 3 = "UserNotAllowedLogEntry", - * 4 = "ExceptionLogEntry", - * 5 = "ElementDeletedLogEntry", - * 6 = "ElementCreatedLogEntry", - * 7 = "ElementEditedLogEntry", - * 8 = "ConfigChangedLogEntry", - * 9 = "LegacyInstockChangedLogEntry", - * 10 = "DatabaseUpdatedLogEntry", - * 11 = "CollectionElementDeleted", - * 12 = "SecurityEventLogEntry", - * 13 = "PartStockChangedLogEntry", - * }) + * @see \App\Tests\Entity\LogSystem\AbstractLogEntryTest */ +#[ORM\Entity(repositoryClass: LogEntryRepository::class)] +#[ORM\Table('log')] +#[ORM\InheritanceType('SINGLE_TABLE')] +#[ORM\DiscriminatorColumn(name: 'type', type: 'smallint')] +#[ORM\DiscriminatorMap([1 => 'UserLoginLogEntry', 2 => 'UserLogoutLogEntry', 3 => 'UserNotAllowedLogEntry', 4 => 'ExceptionLogEntry', 5 => 'ElementDeletedLogEntry', 6 => 'ElementCreatedLogEntry', 7 => 'ElementEditedLogEntry', 8 => 'ConfigChangedLogEntry', 9 => 'LegacyInstockChangedLogEntry', 10 => 'DatabaseUpdatedLogEntry', 11 => 'CollectionElementDeleted', 12 => 'SecurityEventLogEntry', 13 => 'PartStockChangedLogEntry'])] +#[ORM\Index(columns: ['type'], name: 'log_idx_type')] +#[ORM\Index(columns: ['type', 'target_type', 'target_id'], name: 'log_idx_type_target')] +#[ORM\Index(columns: ['datetime'], name: 'log_idx_datetime')] abstract class AbstractLogEntry extends AbstractDBElement { - public const LEVEL_EMERGENCY = 0; - public const LEVEL_ALERT = 1; - public const LEVEL_CRITICAL = 2; - public const LEVEL_ERROR = 3; - public const LEVEL_WARNING = 4; - public const LEVEL_NOTICE = 5; - public const LEVEL_INFO = 6; - public const LEVEL_DEBUG = 7; - - protected const TARGET_TYPE_NONE = 0; - protected const TARGET_TYPE_USER = 1; - protected const TARGET_TYPE_ATTACHEMENT = 2; - protected const TARGET_TYPE_ATTACHEMENTTYPE = 3; - protected const TARGET_TYPE_CATEGORY = 4; - protected const TARGET_TYPE_DEVICE = 5; - protected const TARGET_TYPE_DEVICEPART = 6; - protected const TARGET_TYPE_FOOTPRINT = 7; - protected const TARGET_TYPE_GROUP = 8; - protected const TARGET_TYPE_MANUFACTURER = 9; - protected const TARGET_TYPE_PART = 10; - protected const TARGET_TYPE_STORELOCATION = 11; - protected const TARGET_TYPE_SUPPLIER = 12; - protected const TARGET_TYPE_PARTLOT = 13; - protected const TARGET_TYPE_CURRENCY = 14; - protected const TARGET_TYPE_ORDERDETAIL = 15; - protected const TARGET_TYPE_PRICEDETAIL = 16; - protected const TARGET_TYPE_MEASUREMENTUNIT = 17; - protected const TARGET_TYPE_PARAMETER = 18; - protected const TARGET_TYPE_LABEL_PROFILE = 19; - - /** - * @var array This const is used to convert the numeric level to a PSR-3 compatible log level - */ - protected const LEVEL_ID_TO_STRING = [ - self::LEVEL_EMERGENCY => LogLevel::EMERGENCY, - self::LEVEL_ALERT => LogLevel::ALERT, - self::LEVEL_CRITICAL => LogLevel::CRITICAL, - self::LEVEL_ERROR => LogLevel::ERROR, - self::LEVEL_WARNING => LogLevel::WARNING, - self::LEVEL_NOTICE => LogLevel::NOTICE, - self::LEVEL_INFO => LogLevel::INFO, - self::LEVEL_DEBUG => LogLevel::DEBUG, - ]; - - protected const TARGET_CLASS_MAPPING = [ - self::TARGET_TYPE_USER => User::class, - self::TARGET_TYPE_ATTACHEMENT => Attachment::class, - self::TARGET_TYPE_ATTACHEMENTTYPE => AttachmentType::class, - self::TARGET_TYPE_CATEGORY => Category::class, - self::TARGET_TYPE_DEVICE => Project::class, - self::TARGET_TYPE_DEVICEPART => ProjectBOMEntry::class, - self::TARGET_TYPE_FOOTPRINT => Footprint::class, - self::TARGET_TYPE_GROUP => Group::class, - self::TARGET_TYPE_MANUFACTURER => Manufacturer::class, - self::TARGET_TYPE_PART => Part::class, - self::TARGET_TYPE_STORELOCATION => Storelocation::class, - self::TARGET_TYPE_SUPPLIER => Supplier::class, - self::TARGET_TYPE_PARTLOT => PartLot::class, - self::TARGET_TYPE_CURRENCY => Currency::class, - self::TARGET_TYPE_ORDERDETAIL => Orderdetail::class, - self::TARGET_TYPE_PRICEDETAIL => Pricedetail::class, - self::TARGET_TYPE_MEASUREMENTUNIT => MeasurementUnit::class, - self::TARGET_TYPE_PARAMETER => AbstractParameter::class, - self::TARGET_TYPE_LABEL_PROFILE => LabelProfile::class, - ]; - /** @var User|null The user which has caused this log entry - * @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User", fetch="EAGER") - * @ORM\JoinColumn(name="id_user", nullable=true, onDelete="SET NULL") */ + #[ORM\ManyToOne(targetEntity: User::class, fetch: 'EAGER')] + #[ORM\JoinColumn(name: 'id_user', onDelete: 'SET NULL')] protected ?User $user = null; /** * @var string The username of the user which has caused this log entry (shown if the user is deleted) - * @ORM\Column(type="string", nullable=false) */ + #[ORM\Column(type: Types::STRING)] protected string $username = ''; - /** @var DateTime The datetime the event associated with this log entry has occured - * @ORM\Column(type="datetime", name="datetime") + /** @var \DateTimeInterface The datetime the event associated with this log entry has occured */ - protected ?DateTime $timestamp = null; + #[ORM\Column(name: 'datetime', type: Types::DATETIME_MUTABLE)] + protected \DateTimeInterface $timestamp; - /** @var int The priority level of the associated level. 0 is highest, 7 lowest - * @ORM\Column(type="tinyint", name="level", nullable=false) + /** + * @var LogLevel The priority level of the associated level. 0 is highest, 7 lowest */ - protected int $level; + #[ORM\Column(name: 'level', type: 'tinyint', enumType: LogLevel::class)] + protected LogLevel $level = LogLevel::WARNING; /** @var int The ID of the element targeted by this event - * @ORM\Column(name="target_id", type="integer", nullable=false) */ + #[ORM\Column(name: 'target_id', type: Types::INTEGER)] protected int $target_id = 0; - /** @var int The Type of the targeted element - * @ORM\Column(name="target_type", type="smallint", nullable=false) + /** @var LogTargetType The Type of the targeted element */ - protected int $target_type = 0; + #[ORM\Column(name: 'target_type', type: Types::SMALLINT, enumType: LogTargetType::class)] + protected LogTargetType $target_type = LogTargetType::NONE; /** @var string The type of this log entry, aka the description what has happened. * The mapping between the log entry class and the discriminator column is done by doctrine. @@ -181,14 +103,13 @@ abstract class AbstractLogEntry extends AbstractDBElement protected string $typeString = 'unknown'; /** @var array The extra data in raw (short form) saved in the DB - * @ORM\Column(name="extra", type="json") */ + #[ORM\Column(name: 'extra', type: Types::JSON)] protected array $extra = []; public function __construct() { $this->timestamp = new DateTime(); - $this->level = self::LEVEL_WARNING; } /** @@ -222,14 +143,13 @@ abstract class AbstractLogEntry extends AbstractDBElement */ public function isCLIEntry(): bool { - return strpos($this->username, '!!!CLI ') === 0; + return str_starts_with($this->username, '!!!CLI '); } /** * Marks this log entry as a CLI entry, and set the username of the CLI user. * This removes the association to a user object in database, as CLI users are not really related to logged in * Part-DB users. - * @param string $cli_username * @return $this */ public function setCLIUsername(string $cli_username): self @@ -262,9 +182,9 @@ abstract class AbstractLogEntry extends AbstractDBElement } /** - * Returns the timestamp when the event that caused this log entry happened. + * Returns the timestamp when the event that caused this log entry happened. */ - public function getTimestamp(): DateTime + public function getTimestamp(): \DateTimeInterface { return $this->timestamp; } @@ -274,7 +194,7 @@ abstract class AbstractLogEntry extends AbstractDBElement * * @return $this */ - public function setTimestamp(DateTime $timestamp): self + public function setTimestamp(\DateTimeInterface $timestamp): self { $this->timestamp = $timestamp; @@ -282,16 +202,10 @@ abstract class AbstractLogEntry extends AbstractDBElement } /** - * Get the priority level of this log entry. 0 is highest and 7 lowest level. - * See LEVEL_* consts in this class for more info. + * Get the priority level of this log entry. */ - public function getLevel(): int + public function getLevel(): LogLevel { - //It is always alerting when a wrong int is saved in DB... - if ($this->level < 0 || $this->level > 7) { - return self::LEVEL_ALERT; - } - return $this->level; } @@ -300,13 +214,9 @@ abstract class AbstractLogEntry extends AbstractDBElement * * @return $this */ - public function setLevel(int $level): self + public function setLevel(LogLevel $level): self { - if ($level < 0 || $this->level > 7) { - throw new InvalidArgumentException(sprintf('$level must be between 0 and 7! %d given!', $level)); - } $this->level = $level; - return $this; } @@ -315,7 +225,7 @@ abstract class AbstractLogEntry extends AbstractDBElement */ public function getLevelString(): string { - return self::levelIntToString($this->getLevel()); + return $this->level->toPSR3LevelString(); } /** @@ -325,8 +235,7 @@ abstract class AbstractLogEntry extends AbstractDBElement */ public function setLevelString(string $level): self { - $this->setLevel(self::levelStringToInt($level)); - + LogLevel::fromPSR3LevelString($level); return $this; } @@ -346,11 +255,16 @@ abstract class AbstractLogEntry extends AbstractDBElement */ public function getTargetClass(): ?string { - if (self::TARGET_TYPE_NONE === $this->target_type) { - return null; - } + return $this->target_type->toClass(); + } - return self::targetTypeIdToClass($this->target_type); + /** + * Returns the type of the target element associated with this log entry. + * @return LogTargetType + */ + public function getTargetType(): LogTargetType + { + return $this->target_type; } /** @@ -387,14 +301,14 @@ abstract class AbstractLogEntry extends AbstractDBElement */ public function setTargetElement(?AbstractDBElement $element): self { - if (null === $element) { + if ($element === null) { $this->target_id = 0; - $this->target_type = self::TARGET_TYPE_NONE; + $this->target_type = LogTargetType::NONE; return $this; } - $this->target_type = static::targetTypeClassToID(get_class($element)); + $this->target_type = LogTargetType::fromElementClass($element); $this->target_id = $element->getID(); return $this; @@ -417,75 +331,4 @@ abstract class AbstractLogEntry extends AbstractDBElement return $this->extra; } - /** - * This function converts the internal numeric log level into an PSR3 compatible level string. - * - * @param int $level The numerical log level - * - * @return string The PSR3 compatible level string - */ - final public static function levelIntToString(int $level): string - { - if (!isset(self::LEVEL_ID_TO_STRING[$level])) { - throw new InvalidArgumentException('No level with this int is existing!'); - } - - return self::LEVEL_ID_TO_STRING[$level]; - } - - /** - * This function converts a PSR3 compatible string to the internal numeric level string. - * - * @param string $level the PSR3 compatible string that should be converted - * - * @return int the internal int representation - */ - final public static function levelStringToInt(string $level): int - { - $tmp = array_flip(self::LEVEL_ID_TO_STRING); - if (!isset($tmp[$level])) { - throw new InvalidArgumentException('No level with this string is existing!'); - } - - return $tmp[$level]; - } - - /** - * Converts a target type id to a full qualified class name. - * - * @param int $type_id The target type ID - */ - final public static function targetTypeIdToClass(int $type_id): string - { - if (!isset(self::TARGET_CLASS_MAPPING[$type_id])) { - throw new InvalidArgumentException('No target type with this ID is existing!'); - } - - return self::TARGET_CLASS_MAPPING[$type_id]; - } - - /** - * Convert a class name to a target type ID. - * - * @param string $class The name of the class (FQN) that should be converted to id - * - * @return int the ID of the associated target type ID - */ - final public static function targetTypeClassToID(string $class): int - { - $tmp = array_flip(self::TARGET_CLASS_MAPPING); - //Check if we can use a key directly - if (isset($tmp[$class])) { - return $tmp[$class]; - } - - //Otherwise we have to iterate over everything and check for inheritance - foreach ($tmp as $compare_class => $class_id) { - if (is_a($class, $compare_class, true)) { - return $class_id; - } - } - - throw new InvalidArgumentException('No target ID for this class is existing! (Class: '.$class.')'); - } } diff --git a/src/Entity/LogSystem/CollectionElementDeleted.php b/src/Entity/LogSystem/CollectionElementDeleted.php index 9e646cca..c3980a40 100644 --- a/src/Entity/LogSystem/CollectionElementDeleted.php +++ b/src/Entity/LogSystem/CollectionElementDeleted.php @@ -84,24 +84,22 @@ use App\Entity\UserSystem\User; use Doctrine\ORM\Mapping as ORM; use InvalidArgumentException; -/** - * @ORM\Entity() - * This log entry is created when an element is deleted, that is used in a collection of another entity. - * This is needed to signal time travel, that it has to undelete the deleted entity. - */ +#[ORM\Entity] class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventUndoInterface { + use LogWithEventUndoTrait; + protected string $typeString = 'collection_element_deleted'; - protected int $level = self::LEVEL_INFO; public function __construct(AbstractDBElement $changed_element, string $collection_name, AbstractDBElement $deletedElement) { parent::__construct(); - $this->level = self::LEVEL_INFO; + $this->level = LogLevel::INFO; + $this->setTargetElement($changed_element); $this->extra['n'] = $collection_name; - $this->extra['c'] = self::targetTypeClassToID(get_class($deletedElement)); + $this->extra['c'] = LogTargetType::fromElementClass($deletedElement)->value; $this->extra['i'] = $deletedElement->getID(); if ($deletedElement instanceof NamedElementInterface) { $this->extra['o'] = $deletedElement->getName(); @@ -131,7 +129,7 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU public function getDeletedElementClass(): string { //The class name of our target element - $tmp = self::targetTypeIdToClass($this->extra['c']); + $tmp = LogTargetType::from($this->extra['c'])->toClass(); $reflection_class = new \ReflectionClass($tmp); //If the class is abstract, we have to map it to an instantiable class @@ -145,8 +143,6 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU /** * This functions maps an abstract class name derived from the extra c element to an instantiable class name (based on the target element of this log entry). * For example if the target element is a part and the extra c element is "App\Entity\Attachments\Attachment", this function will return "App\Entity\Attachments\PartAttachment". - * @param string $abstract_class - * @return string */ private function resolveAbstractClassToInstantiableClass(string $abstract_class): string { @@ -222,39 +218,4 @@ class CollectionElementDeleted extends AbstractLogEntry implements LogWithEventU { return $this->extra['i']; } - - public function isUndoEvent(): bool - { - return isset($this->extra['u']); - } - - public function getUndoEventID(): ?int - { - return $this->extra['u'] ?? null; - } - - public function setUndoneEvent(AbstractLogEntry $event, string $mode = 'undo'): LogWithEventUndoInterface - { - $this->extra['u'] = $event->getID(); - - if ('undo' === $mode) { - $this->extra['um'] = 1; - } elseif ('revert' === $mode) { - $this->extra['um'] = 2; - } else { - throw new InvalidArgumentException('Passed invalid $mode!'); - } - - return $this; - } - - public function getUndoMode(): string - { - $mode_int = $this->extra['um'] ?? 1; - if (1 === $mode_int) { - return 'undo'; - } - - return 'revert'; - } } diff --git a/src/Entity/LogSystem/ConfigChangedLogEntry.php b/src/Entity/LogSystem/ConfigChangedLogEntry.php index 543886bf..68f8edf3 100644 --- a/src/Entity/LogSystem/ConfigChangedLogEntry.php +++ b/src/Entity/LogSystem/ConfigChangedLogEntry.php @@ -25,9 +25,7 @@ namespace App\Entity\LogSystem; use App\Exceptions\LogEntryObsoleteException; use Doctrine\ORM\Mapping as ORM; -/** - * @ORM\Entity() - */ +#[ORM\Entity] class ConfigChangedLogEntry extends AbstractLogEntry { protected string $typeString = 'config_changed'; diff --git a/src/Entity/LogSystem/DatabaseUpdatedLogEntry.php b/src/Entity/LogSystem/DatabaseUpdatedLogEntry.php index 0f85ba11..6e137373 100644 --- a/src/Entity/LogSystem/DatabaseUpdatedLogEntry.php +++ b/src/Entity/LogSystem/DatabaseUpdatedLogEntry.php @@ -24,9 +24,7 @@ namespace App\Entity\LogSystem; use Doctrine\ORM\Mapping as ORM; -/** - * @ORM\Entity() - */ +#[ORM\Entity] class DatabaseUpdatedLogEntry extends AbstractLogEntry { protected string $typeString = 'database_updated'; diff --git a/src/Entity/LogSystem/ElementCreatedLogEntry.php b/src/Entity/LogSystem/ElementCreatedLogEntry.php index c78e6df0..7d5968a8 100644 --- a/src/Entity/LogSystem/ElementCreatedLogEntry.php +++ b/src/Entity/LogSystem/ElementCreatedLogEntry.php @@ -27,25 +27,26 @@ use App\Entity\Contracts\LogWithCommentInterface; use App\Entity\Contracts\LogWithEventUndoInterface; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; +use App\Services\LogSystem\EventUndoMode; use Doctrine\ORM\Mapping as ORM; use InvalidArgumentException; -/** - * @ORM\Entity() - */ +#[ORM\Entity] class ElementCreatedLogEntry extends AbstractLogEntry implements LogWithCommentInterface, LogWithEventUndoInterface { + use LogWithEventUndoTrait; + protected string $typeString = 'element_created'; public function __construct(AbstractDBElement $new_element) { parent::__construct(); - $this->level = self::LEVEL_INFO; + $this->level = LogLevel::INFO; $this->setTargetElement($new_element); //Creation of new users is maybe more interesting... if ($new_element instanceof User || $new_element instanceof Group) { - $this->level = self::LEVEL_NOTICE; + $this->level = LogLevel::NOTICE; } } @@ -81,39 +82,4 @@ class ElementCreatedLogEntry extends AbstractLogEntry implements LogWithCommentI return $this; } - - public function isUndoEvent(): bool - { - return isset($this->extra['u']); - } - - public function getUndoEventID(): ?int - { - return $this->extra['u'] ?? null; - } - - public function setUndoneEvent(AbstractLogEntry $event, string $mode = 'undo'): LogWithEventUndoInterface - { - $this->extra['u'] = $event->getID(); - - if ('undo' === $mode) { - $this->extra['um'] = 1; - } elseif ('revert' === $mode) { - $this->extra['um'] = 2; - } else { - throw new InvalidArgumentException('Passed invalid $mode!'); - } - - return $this; - } - - public function getUndoMode(): string - { - $mode_int = $this->extra['um'] ?? 1; - if (1 === $mode_int) { - return 'undo'; - } - - return 'revert'; - } } diff --git a/src/Entity/LogSystem/ElementDeletedLogEntry.php b/src/Entity/LogSystem/ElementDeletedLogEntry.php index c20bf87c..836e8d60 100644 --- a/src/Entity/LogSystem/ElementDeletedLogEntry.php +++ b/src/Entity/LogSystem/ElementDeletedLogEntry.php @@ -29,25 +29,26 @@ use App\Entity\Contracts\NamedElementInterface; use App\Entity\Contracts\TimeTravelInterface; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; +use App\Services\LogSystem\EventUndoMode; use Doctrine\ORM\Mapping as ORM; use InvalidArgumentException; -/** - * @ORM\Entity() - */ +#[ORM\Entity] class ElementDeletedLogEntry extends AbstractLogEntry implements TimeTravelInterface, LogWithCommentInterface, LogWithEventUndoInterface { protected string $typeString = 'element_deleted'; + use LogWithEventUndoTrait; + public function __construct(AbstractDBElement $deleted_element) { parent::__construct(); - $this->level = self::LEVEL_INFO; + $this->level = LogLevel::INFO; $this->setTargetElement($deleted_element); //Deletion of a user is maybe more interesting... if ($deleted_element instanceof User || $deleted_element instanceof Group) { - $this->level = self::LEVEL_NOTICE; + $this->level = LogLevel::NOTICE; } } @@ -114,39 +115,4 @@ class ElementDeletedLogEntry extends AbstractLogEntry implements TimeTravelInter return $this; } - - public function isUndoEvent(): bool - { - return isset($this->extra['u']); - } - - public function getUndoEventID(): ?int - { - return $this->extra['u'] ?? null; - } - - public function setUndoneEvent(AbstractLogEntry $event, string $mode = 'undo'): LogWithEventUndoInterface - { - $this->extra['u'] = $event->getID(); - - if ('undo' === $mode) { - $this->extra['um'] = 1; - } elseif ('revert' === $mode) { - $this->extra['um'] = 2; - } else { - throw new InvalidArgumentException('Passed invalid $mode!'); - } - - return $this; - } - - public function getUndoMode(): string - { - $mode_int = $this->extra['um'] ?? 1; - if (1 === $mode_int) { - return 'undo'; - } - - return 'revert'; - } } diff --git a/src/Entity/LogSystem/ElementEditedLogEntry.php b/src/Entity/LogSystem/ElementEditedLogEntry.php index fb5f2e5c..4279ac63 100644 --- a/src/Entity/LogSystem/ElementEditedLogEntry.php +++ b/src/Entity/LogSystem/ElementEditedLogEntry.php @@ -30,17 +30,17 @@ use App\Entity\Contracts\TimeTravelInterface; use Doctrine\ORM\Mapping as ORM; use InvalidArgumentException; -/** - * @ORM\Entity() - */ +#[ORM\Entity] class ElementEditedLogEntry extends AbstractLogEntry implements TimeTravelInterface, LogWithCommentInterface, LogWithEventUndoInterface, LogWithNewDataInterface { + use LogWithEventUndoTrait; + protected string $typeString = 'element_edited'; public function __construct(AbstractDBElement $changed_element) { parent::__construct(); - $this->level = self::LEVEL_INFO; + $this->level = LogLevel::INFO; $this->setTargetElement($changed_element); } @@ -141,39 +141,4 @@ class ElementEditedLogEntry extends AbstractLogEntry implements TimeTravelInterf return $this; } - - public function isUndoEvent(): bool - { - return isset($this->extra['u']); - } - - public function getUndoEventID(): ?int - { - return $this->extra['u'] ?? null; - } - - public function setUndoneEvent(AbstractLogEntry $event, string $mode = 'undo'): LogWithEventUndoInterface - { - $this->extra['u'] = $event->getID(); - - if ('undo' === $mode) { - $this->extra['um'] = 1; - } elseif ('revert' === $mode) { - $this->extra['um'] = 2; - } else { - throw new InvalidArgumentException('Passed invalid $mode!'); - } - - return $this; - } - - public function getUndoMode(): string - { - $mode_int = $this->extra['um'] ?? 1; - if (1 === $mode_int) { - return 'undo'; - } - - return 'revert'; - } } diff --git a/src/Entity/LogSystem/ExceptionLogEntry.php b/src/Entity/LogSystem/ExceptionLogEntry.php index dc9a7f32..e8fb06f9 100644 --- a/src/Entity/LogSystem/ExceptionLogEntry.php +++ b/src/Entity/LogSystem/ExceptionLogEntry.php @@ -25,9 +25,7 @@ namespace App\Entity\LogSystem; use App\Exceptions\LogEntryObsoleteException; use Doctrine\ORM\Mapping as ORM; -/** - * @ORM\Entity() - */ +#[ORM\Entity] class ExceptionLogEntry extends AbstractLogEntry { protected string $typeString = 'exception'; diff --git a/src/Entity/LogSystem/LegacyInstockChangedLogEntry.php b/src/Entity/LogSystem/LegacyInstockChangedLogEntry.php index 9af3dd69..27f7afe4 100644 --- a/src/Entity/LogSystem/LegacyInstockChangedLogEntry.php +++ b/src/Entity/LogSystem/LegacyInstockChangedLogEntry.php @@ -24,9 +24,7 @@ namespace App\Entity\LogSystem; use Doctrine\ORM\Mapping as ORM; -/** - * @ORM\Entity() - */ +#[ORM\Entity] class LegacyInstockChangedLogEntry extends AbstractLogEntry { protected string $typeString = 'instock_changed'; diff --git a/src/Entity/LogSystem/LogLevel.php b/src/Entity/LogSystem/LogLevel.php new file mode 100644 index 00000000..98fb3649 --- /dev/null +++ b/src/Entity/LogSystem/LogLevel.php @@ -0,0 +1,117 @@ +. + */ + +namespace App\Entity\LogSystem; + +use \Psr\Log\LogLevel as PSRLogLevel; + +enum LogLevel: int +{ + case EMERGENCY = 0; + case ALERT = 1; + case CRITICAL = 2; + case ERROR = 3; + case WARNING = 4; + case NOTICE = 5; + case INFO = 6; + case DEBUG = 7; + + /** + * Converts the current log level to a PSR-3 log level string. + * @return string + */ + public function toPSR3LevelString(): string + { + return match ($this) { + self::EMERGENCY => PSRLogLevel::EMERGENCY, + self::ALERT => PSRLogLevel::ALERT, + self::CRITICAL => PSRLogLevel::CRITICAL, + self::ERROR => PSRLogLevel::ERROR, + self::WARNING => PSRLogLevel::WARNING, + self::NOTICE => PSRLogLevel::NOTICE, + self::INFO => PSRLogLevel::INFO, + self::DEBUG => PSRLogLevel::DEBUG, + }; + } + + /** + * Creates a log level (enum) from a PSR-3 log level string. + * @param string $level + * @return self + */ + public static function fromPSR3LevelString(string $level): self + { + return match ($level) { + PSRLogLevel::EMERGENCY => self::EMERGENCY, + PSRLogLevel::ALERT => self::ALERT, + PSRLogLevel::CRITICAL => self::CRITICAL, + PSRLogLevel::ERROR => self::ERROR, + PSRLogLevel::WARNING => self::WARNING, + PSRLogLevel::NOTICE => self::NOTICE, + PSRLogLevel::INFO => self::INFO, + PSRLogLevel::DEBUG => self::DEBUG, + default => throw new \InvalidArgumentException("Invalid log level: $level"), + }; + } + + /** + * Checks if the current log level is more important than the given one. + * @param LogLevel $other + * @return bool + */ + public function moreImportThan(self $other): bool + { + //Smaller values are more important + return $this->value < $other->value; + } + + /** + * Checks if the current log level is more important or equal than the given one. + * @param LogLevel $other + * @return bool + */ + public function moreImportOrEqualThan(self $other): bool + { + //Smaller values are more important + return $this->value <= $other->value; + } + + /** + * Checks if the current log level is less important than the given one. + * @param LogLevel $other + * @return bool + */ + public function lessImportThan(self $other): bool + { + //Bigger values are less important + return $this->value > $other->value; + } + + /** + * Checks if the current log level is less important or equal than the given one. + * @param LogLevel $other + * @return bool + */ + public function lessImportOrEqualThan(self $other): bool + { + //Bigger values are less important + return $this->value >= $other->value; + } +} diff --git a/src/Entity/LogSystem/LogTargetType.php b/src/Entity/LogSystem/LogTargetType.php new file mode 100644 index 00000000..38d1f1ed --- /dev/null +++ b/src/Entity/LogSystem/LogTargetType.php @@ -0,0 +1,123 @@ +. + */ + +namespace App\Entity\LogSystem; + +use App\Entity\Attachments\Attachment; +use App\Entity\Attachments\AttachmentType; +use App\Entity\LabelSystem\LabelProfile; +use App\Entity\Parameters\AbstractParameter; +use App\Entity\Parts\Category; +use App\Entity\Parts\Footprint; +use App\Entity\Parts\Manufacturer; +use App\Entity\Parts\MeasurementUnit; +use App\Entity\Parts\Part; +use App\Entity\Parts\PartLot; +use App\Entity\Parts\Storelocation; +use App\Entity\Parts\Supplier; +use App\Entity\PriceInformations\Currency; +use App\Entity\PriceInformations\Orderdetail; +use App\Entity\PriceInformations\Pricedetail; +use App\Entity\ProjectSystem\Project; +use App\Entity\ProjectSystem\ProjectBOMEntry; +use App\Entity\UserSystem\Group; +use App\Entity\UserSystem\User; + +enum LogTargetType: int +{ + case NONE = 0; + case USER = 1; + case ATTACHMENT = 2; + case ATTACHMENT_TYPE = 3; + case CATEGORY = 4; + case PROJECT = 5; + case BOM_ENTRY = 6; + case FOOTPRINT = 7; + case GROUP = 8; + case MANUFACTURER = 9; + case PART = 10; + case STORELOCATION = 11; + case SUPPLIER = 12; + case PART_LOT = 13; + case CURRENCY = 14; + case ORDERDETAIL = 15; + case PRICEDETAIL = 16; + case MEASUREMENT_UNIT = 17; + case PARAMETER = 18; + case LABEL_PROFILE = 19; + + /** + * Returns the class name of the target type or null if the target type is NONE. + * @return string|null + */ + public function toClass(): ?string + { + return match ($this) { + self::NONE => null, + self::USER => User::class, + self::ATTACHMENT => Attachment::class, + self::ATTACHMENT_TYPE => AttachmentType::class, + self::CATEGORY => Category::class, + self::PROJECT => Project::class, + self::BOM_ENTRY => ProjectBOMEntry::class, + self::FOOTPRINT => Footprint::class, + self::GROUP => Group::class, + self::MANUFACTURER => Manufacturer::class, + self::PART => Part::class, + self::STORELOCATION => Storelocation::class, + self::SUPPLIER => Supplier::class, + self::PART_LOT => PartLot::class, + self::CURRENCY => Currency::class, + self::ORDERDETAIL => Orderdetail::class, + self::PRICEDETAIL => Pricedetail::class, + self::MEASUREMENT_UNIT => MeasurementUnit::class, + self::PARAMETER => AbstractParameter::class, + self::LABEL_PROFILE => LabelProfile::class, + }; + } + + /** + * Determines the target type from the given class name or object. + * @param object|string $element + * @phpstan-param object|class-string $element + * @return self + */ + public static function fromElementClass(object|string $element): self + { + //Iterate over all possible types + foreach (self::cases() as $case) { + $class = $case->toClass(); + + //Skip NONE + if ($class === null) { + continue; + } + + //Check if the given element is a instance of the class + if (is_a($element, $class, true)) { + return $case; + } + } + + $elementClass = is_object($element) ? get_class($element) : $element; + //If no matching type was found, throw an exception + throw new \InvalidArgumentException("The given class $elementClass is not a valid log target type."); + } +} diff --git a/src/Entity/LogSystem/LogWithEventUndoTrait.php b/src/Entity/LogSystem/LogWithEventUndoTrait.php new file mode 100644 index 00000000..16568241 --- /dev/null +++ b/src/Entity/LogSystem/LogWithEventUndoTrait.php @@ -0,0 +1,51 @@ +. + */ + +namespace App\Entity\LogSystem; + +use App\Entity\Contracts\LogWithEventUndoInterface; +use App\Services\LogSystem\EventUndoMode; + +trait LogWithEventUndoTrait +{ + public function isUndoEvent(): bool + { + return isset($this->extra['u']); + } + + public function getUndoEventID(): ?int + { + return $this->extra['u'] ?? null; + } + + public function setUndoneEvent(AbstractLogEntry $event, EventUndoMode $mode = EventUndoMode::UNDO): LogWithEventUndoInterface + { + $this->extra['u'] = $event->getID(); + $this->extra['um'] = $mode->toExtraInt(); + + return $this; + } + + public function getUndoMode(): EventUndoMode + { + $mode_int = $this->extra['um'] ?? 1; + return EventUndoMode::fromExtraInt($mode_int); + } +} \ No newline at end of file diff --git a/src/Entity/LogSystem/PartStockChangeType.php b/src/Entity/LogSystem/PartStockChangeType.php new file mode 100644 index 00000000..bbd574a7 --- /dev/null +++ b/src/Entity/LogSystem/PartStockChangeType.php @@ -0,0 +1,51 @@ +. + */ + +namespace App\Entity\LogSystem; + +enum PartStockChangeType: string +{ + case ADD = "add"; + case WITHDRAW = "withdraw"; + case MOVE = "move"; + + /** + * Converts the type to a short representation usable in the extra field of the log entry. + * @return string + */ + public function toExtraShortType(): string + { + return match ($this) { + self::ADD => 'a', + self::WITHDRAW => 'w', + self::MOVE => 'm', + }; + } + + public static function fromExtraShortType(string $value): self + { + return match ($value) { + 'a' => self::ADD, + 'w' => self::WITHDRAW, + 'm' => self::MOVE, + default => throw new \InvalidArgumentException("Invalid short type: $value"), + }; + } +} \ No newline at end of file diff --git a/src/Entity/LogSystem/PartStockChangedLogEntry.php b/src/Entity/LogSystem/PartStockChangedLogEntry.php index 44852076..63288d0d 100644 --- a/src/Entity/LogSystem/PartStockChangedLogEntry.php +++ b/src/Entity/LogSystem/PartStockChangedLogEntry.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\LogSystem; use App\Entity\Parts\PartLot; use Doctrine\ORM\Mapping as ORM; -/** - * @ORM\Entity() - */ +#[ORM\Entity] class PartStockChangedLogEntry extends AbstractLogEntry { - public const TYPE_ADD = "add"; - public const TYPE_WITHDRAW = "withdraw"; - public const TYPE_MOVE = "move"; - protected string $typeString = 'part_stock_changed'; protected const COMMENT_MAX_LENGTH = 300; /** * Creates a new part stock changed log entry. - * @param string $type The type of the log entry. One of the TYPE_* constants. + * @param PartStockChangeType $type The type of the log entry. * @param PartLot $lot The part lot which has been changed. * @param float $old_stock The old stock of the lot. * @param float $new_stock The new stock of the lot. @@ -46,32 +42,28 @@ class PartStockChangedLogEntry extends AbstractLogEntry * @param string $comment The comment associated with the change. * @param PartLot|null $move_to_target The target lot if the type is TYPE_MOVE. */ - protected function __construct(string $type, PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, ?PartLot $move_to_target = null) + protected function __construct(PartStockChangeType $type, PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, ?PartLot $move_to_target = null) { parent::__construct(); - if (!in_array($type, [self::TYPE_ADD, self::TYPE_WITHDRAW, self::TYPE_MOVE], true)) { - throw new \InvalidArgumentException('Invalid type for PartStockChangedLogEntry!'); - } - //Same as every other element change log entry - $this->level = self::LEVEL_INFO; + $this->level = LogLevel::INFO; $this->setTargetElement($lot); $this->typeString = 'part_stock_changed'; $this->extra = array_merge($this->extra, [ - 't' => $this->typeToShortType($type), + 't' => $type->toExtraShortType(), 'o' => $old_stock, 'n' => $new_stock, 'p' => $new_total_part_instock, ]); - if (!empty($comment)) { + if ($comment !== '') { $this->extra['c'] = mb_strimwidth($comment, 0, self::COMMENT_MAX_LENGTH, '...'); } - if ($move_to_target) { - if ($type !== self::TYPE_MOVE) { + if ($move_to_target instanceof PartLot) { + if ($type !== PartStockChangeType::MOVE) { throw new \InvalidArgumentException('The move_to_target parameter can only be set if the type is "move"!'); } @@ -86,11 +78,11 @@ class PartStockChangedLogEntry extends AbstractLogEntry * @param float $new_stock The new stock of the lot. * @param float $new_total_part_instock The new total instock of the part. * @param string $comment The comment associated with the change. - * @return static + * @return self */ public static function add(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment): self { - return new self(self::TYPE_ADD, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment); + return new self(PartStockChangeType::ADD, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment); } /** @@ -100,11 +92,11 @@ class PartStockChangedLogEntry extends AbstractLogEntry * @param float $new_stock The new stock of the lot. * @param float $new_total_part_instock The new total instock of the part. * @param string $comment The comment associated with the change. - * @return static + * @return self */ public static function withdraw(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment): self { - return new self(self::TYPE_WITHDRAW, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment); + return new self(PartStockChangeType::WITHDRAW, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment); } /** @@ -118,21 +110,20 @@ class PartStockChangedLogEntry extends AbstractLogEntry */ public static function move(PartLot $lot, float $old_stock, float $new_stock, float $new_total_part_instock, string $comment, PartLot $move_to_target): self { - return new self(self::TYPE_MOVE, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment, $move_to_target); + return new self(PartStockChangeType::MOVE, $lot, $old_stock, $new_stock, $new_total_part_instock, $comment, $move_to_target); } /** * Returns the instock change type of this entry - * @return string One of the TYPE_* constants. + * @return PartStockChangeType */ - public function getInstockChangeType(): string + public function getInstockChangeType(): PartStockChangeType { - return $this->shortTypeToType($this->extra['t']); + return PartStockChangeType::fromExtraShortType($this->extra['t']); } /** * Returns the old stock of the lot. - * @return float */ public function getOldStock(): float { @@ -141,7 +132,6 @@ class PartStockChangedLogEntry extends AbstractLogEntry /** * Returns the new stock of the lot. - * @return float */ public function getNewStock(): float { @@ -150,7 +140,6 @@ class PartStockChangedLogEntry extends AbstractLogEntry /** * Returns the new total instock of the part. - * @return float */ public function getNewTotalPartInstock(): float { @@ -159,7 +148,6 @@ class PartStockChangedLogEntry extends AbstractLogEntry /** * Returns the comment associated with the change. - * @return string */ public function getComment(): string { @@ -168,7 +156,6 @@ class PartStockChangedLogEntry extends AbstractLogEntry /** * Gets the difference between the old and the new stock value of the lot as a positive number. - * @return float */ public function getChangeAmount(): float { @@ -177,48 +164,9 @@ class PartStockChangedLogEntry extends AbstractLogEntry /** * Returns the target lot ID (where the instock was moved to) if the type is TYPE_MOVE. - * @return int|null */ public function getMoveToTargetID(): ?int { return $this->extra['m'] ?? null; } - - /** - * Converts the human-readable type (TYPE_* consts) to the version stored in DB - * @param string $type - * @return string - */ - protected function typeToShortType(string $type): string - { - switch ($type) { - case self::TYPE_ADD: - return 'a'; - case self::TYPE_WITHDRAW: - return 'w'; - case self::TYPE_MOVE: - return 'm'; - default: - throw new \InvalidArgumentException('Invalid type: '.$type); - } - } - - /** - * Converts the short type stored in DB to the human-readable type (TYPE_* consts). - * @param string $short_type - * @return string - */ - protected function shortTypeToType(string $short_type): string - { - switch ($short_type) { - case 'a': - return self::TYPE_ADD; - case 'w': - return self::TYPE_WITHDRAW; - case 'm': - return self::TYPE_MOVE; - default: - throw new \InvalidArgumentException('Invalid short type: '.$short_type); - } - } -} \ No newline at end of file +} diff --git a/src/Entity/LogSystem/SecurityEventLogEntry.php b/src/Entity/LogSystem/SecurityEventLogEntry.php index 2113beb9..b1b6227a 100644 --- a/src/Entity/LogSystem/SecurityEventLogEntry.php +++ b/src/Entity/LogSystem/SecurityEventLogEntry.php @@ -50,12 +50,11 @@ use Symfony\Component\HttpFoundation\IpUtils; /** * This log entry is created when something security related to a user happens. - * - * @ORM\Entity() */ +#[ORM\Entity] class SecurityEventLogEntry extends AbstractLogEntry { - public const SECURITY_TYPE_MAPPING = [ + final public const SECURITY_TYPE_MAPPING = [ 0 => SecurityEvents::PASSWORD_CHANGED, 1 => SecurityEvents::PASSWORD_RESET, 2 => SecurityEvents::BACKUP_KEYS_RESET, @@ -70,10 +69,9 @@ class SecurityEventLogEntry extends AbstractLogEntry public function __construct(string $type, string $ip_address, bool $anonymize = true) { parent::__construct(); - $this->level = self::LEVEL_INFO; $this->setIPAddress($ip_address, $anonymize); $this->setEventType($type); - $this->level = self::LEVEL_NOTICE; + $this->level = LogLevel::NOTICE; } public function setTargetElement(?AbstractDBElement $element): AbstractLogEntry diff --git a/src/Entity/LogSystem/UserLoginLogEntry.php b/src/Entity/LogSystem/UserLoginLogEntry.php index d1acaa6e..c9e6bc21 100644 --- a/src/Entity/LogSystem/UserLoginLogEntry.php +++ b/src/Entity/LogSystem/UserLoginLogEntry.php @@ -27,9 +27,8 @@ use Symfony\Component\HttpFoundation\IpUtils; /** * This log entry is created when a user logs in. - * - * @ORM\Entity() */ +#[ORM\Entity] class UserLoginLogEntry extends AbstractLogEntry { protected string $typeString = 'user_login'; @@ -37,7 +36,7 @@ class UserLoginLogEntry extends AbstractLogEntry public function __construct(string $ip_address, bool $anonymize = true) { parent::__construct(); - $this->level = self::LEVEL_INFO; + $this->level = LogLevel::INFO; $this->setIPAddress($ip_address, $anonymize); } diff --git a/src/Entity/LogSystem/UserLogoutLogEntry.php b/src/Entity/LogSystem/UserLogoutLogEntry.php index 43a98fb6..ba52de87 100644 --- a/src/Entity/LogSystem/UserLogoutLogEntry.php +++ b/src/Entity/LogSystem/UserLogoutLogEntry.php @@ -25,9 +25,7 @@ namespace App\Entity\LogSystem; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\HttpFoundation\IpUtils; -/** - * @ORM\Entity() - */ +#[ORM\Entity] class UserLogoutLogEntry extends AbstractLogEntry { protected string $typeString = 'user_logout'; @@ -35,7 +33,7 @@ class UserLogoutLogEntry extends AbstractLogEntry public function __construct(string $ip_address, bool $anonymize = true) { parent::__construct(); - $this->level = self::LEVEL_INFO; + $this->level = LogLevel::INFO; $this->setIPAddress($ip_address, $anonymize); } diff --git a/src/Entity/LogSystem/UserNotAllowedLogEntry.php b/src/Entity/LogSystem/UserNotAllowedLogEntry.php index 5d4e3acd..c570a012 100644 --- a/src/Entity/LogSystem/UserNotAllowedLogEntry.php +++ b/src/Entity/LogSystem/UserNotAllowedLogEntry.php @@ -24,9 +24,7 @@ namespace App\Entity\LogSystem; use Doctrine\ORM\Mapping as ORM; -/** - * @ORM\Entity() - */ +#[ORM\Entity] class UserNotAllowedLogEntry extends AbstractLogEntry { protected string $typeString = 'user_not_allowed'; @@ -34,7 +32,7 @@ class UserNotAllowedLogEntry extends AbstractLogEntry public function __construct(string $path) { parent::__construct(); - $this->level = static::LEVEL_WARNING; + $this->level = LogLevel::WARNING; $this->extra['a'] = $path; } diff --git a/src/Entity/Parameters/AbstractParameter.php b/src/Entity/Parameters/AbstractParameter.php index 9281970c..4a5cb40a 100644 --- a/src/Entity/Parameters/AbstractParameter.php +++ b/src/Entity/Parameters/AbstractParameter.php @@ -41,6 +41,8 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use App\Repository\ParameterRepository; +use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractNamedDBElement; use Doctrine\ORM\Mapping as ORM; @@ -51,91 +53,75 @@ use Symfony\Component\Validator\Constraints as Assert; use function sprintf; -/** - * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") - * @ORM\Table("parameters", indexes={ - * @ORM\Index(name="parameter_name_idx", columns={"name"}), - * @ORM\Index(name="parameter_group_idx", columns={"param_group"}), - * @ORM\Index(name="parameter_type_element_idx", columns={"type", "element_id"}) - * }) - * @ORM\InheritanceType("SINGLE_TABLE") - * @ORM\DiscriminatorColumn(name="type", type="smallint") - * @ORM\DiscriminatorMap({ - * 0 = "CategoryParameter", - * 1 = "CurrencyParameter", - * 2 = "ProjectParameter", - * 3 = "FootprintParameter", - * 4 = "GroupParameter", - * 5 = "ManufacturerParameter", - * 6 = "MeasurementUnitParameter", - * 7 = "PartParameter", - * 8 = "StorelocationParameter", - * 9 = "SupplierParameter", - * 10 = "AttachmentTypeParameter" - * }) - */ +#[ORM\Entity(repositoryClass: ParameterRepository::class)] +#[ORM\InheritanceType('SINGLE_TABLE')] +#[ORM\DiscriminatorColumn(name: 'type', type: 'smallint')] +#[ORM\DiscriminatorMap([0 => 'CategoryParameter', 1 => 'CurrencyParameter', 2 => 'ProjectParameter', 3 => 'FootprintParameter', 4 => 'GroupParameter', 5 => 'ManufacturerParameter', 6 => 'MeasurementUnitParameter', 7 => 'PartParameter', 8 => 'StorelocationParameter', 9 => 'SupplierParameter', 10 => 'AttachmentTypeParameter'])] +#[ORM\Table('parameters')] +#[ORM\Index(name: 'parameter_name_idx', columns: ['name'])] +#[ORM\Index(name: 'parameter_group_idx', columns: ['param_group'])] +#[ORM\Index(name: 'parameter_type_element_idx', columns: ['type', 'element_id'])] abstract class AbstractParameter extends AbstractNamedDBElement { /** * @var string The class of the element that can be passed to this attachment. Must be overridden in subclasses. */ - public const ALLOWED_ELEMENT_CLASS = ''; + protected const ALLOWED_ELEMENT_CLASS = ''; /** * @var string The mathematical symbol for this specification. Can be rendered pretty later. Should be short - * @Assert\Length(max=20) - * @ORM\Column(type="string", nullable=false) - * @Groups({"full"}) */ + #[Assert\Length(max: 20)] + #[Groups(['full'])] + #[ORM\Column(type: Types::STRING)] protected string $symbol = ''; /** * @var float|null the guaranteed minimum value of this property - * @Assert\Type({"float","null"}) - * @Assert\LessThanOrEqual(propertyPath="value_typical", message="parameters.validator.min_lesser_typical") - * @Assert\LessThan(propertyPath="value_max", message="parameters.validator.min_lesser_max") - * @ORM\Column(type="float", nullable=true) - * @Groups({"full"}) */ + #[Assert\Type(['float', null])] + #[Assert\LessThanOrEqual(propertyPath: 'value_typical', message: 'parameters.validator.min_lesser_typical')] + #[Assert\LessThan(propertyPath: 'value_max', message: 'parameters.validator.min_lesser_max')] + #[Groups(['full'])] + #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $value_min = null; /** * @var float|null the typical value of this property - * @Assert\Type({"null", "float"}) - * @ORM\Column(type="float", nullable=true) - * @Groups({"full"}) */ + #[Assert\Type([null, 'float'])] + #[Groups(['full'])] + #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $value_typical = null; /** * @var float|null the maximum value of this property - * @Assert\Type({"float", "null"}) - * @Assert\GreaterThanOrEqual(propertyPath="value_typical", message="parameters.validator.max_greater_typical") - * @ORM\Column(type="float", nullable=true) - * @Groups({"full"}) */ + #[Assert\Type(['float', null])] + #[Assert\GreaterThanOrEqual(propertyPath: 'value_typical', message: 'parameters.validator.max_greater_typical')] + #[Groups(['full'])] + #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $value_max = null; /** * @var string The unit in which the value values are given (e.g. V) - * @ORM\Column(type="string", nullable=false) - * @Groups({"full"}) */ + #[Groups(['full'])] + #[ORM\Column(type: Types::STRING)] protected string $unit = ''; /** * @var string a text value for the given property - * @ORM\Column(type="string", nullable=false) - * @Groups({"full"}) */ + #[Groups(['full'])] + #[ORM\Column(type: Types::STRING)] protected string $value_text = ''; /** * @var string the group this parameter belongs to - * @ORM\Column(type="string", nullable=false, name="param_group") - * @Groups({"full"}) - * @Groups({"full"}) */ + #[Groups(['full'])] + #[ORM\Column(type: Types::STRING, name: 'param_group')] protected string $group = ''; /** @@ -202,7 +188,7 @@ abstract class AbstractParameter extends AbstractNamedDBElement $str .= ')'; } - if ($this->value_text) { + if ($this->value_text !== '' && $this->value_text !== '0') { $str .= ' ['.$this->value_text.']'; } @@ -332,7 +318,6 @@ abstract class AbstractParameter extends AbstractNamedDBElement /** * Sets the typical value of this property. * - * @param float|null $value_typical * * @return $this */ @@ -409,10 +394,19 @@ abstract class AbstractParameter extends AbstractNamedDBElement protected function formatWithUnit(float $value, string $format = '%g'): string { $str = sprintf($format, $value); - if (!empty($this->unit)) { + if ($this->unit !== '') { return $str.' '.$this->unit; } return $str; } + + /** + * Returns the class of the element that is allowed to be associated with this attachment. + * @return string + */ + public function getElementClass(): string + { + return static::ALLOWED_ELEMENT_CLASS; + } } diff --git a/src/Entity/Parameters/AttachmentTypeParameter.php b/src/Entity/Parameters/AttachmentTypeParameter.php index 8a161883..2ac9dc09 100644 --- a/src/Entity/Parameters/AttachmentTypeParameter.php +++ b/src/Entity/Parameters/AttachmentTypeParameter.php @@ -41,22 +41,21 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use App\Repository\ParameterRepository; use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractDBElement; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -/** - * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") - * @UniqueEntity(fields={"name", "group", "element"}) - */ +#[UniqueEntity(fields: ['name', 'group', 'element'])] +#[ORM\Entity(repositoryClass: ParameterRepository::class)] class AttachmentTypeParameter extends AbstractParameter { - public const ALLOWED_ELEMENT_CLASS = AttachmentType::class; + final public const ALLOWED_ELEMENT_CLASS = AttachmentType::class; /** * @var AttachmentType the element this para is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Attachments\AttachmentType", inversedBy="parameters") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: AttachmentType::class, inversedBy: 'parameters')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AbstractDBElement $element = null; } diff --git a/src/Entity/Parameters/CategoryParameter.php b/src/Entity/Parameters/CategoryParameter.php index cce90a86..8aa4f29c 100644 --- a/src/Entity/Parameters/CategoryParameter.php +++ b/src/Entity/Parameters/CategoryParameter.php @@ -41,22 +41,21 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\Category; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -/** - * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") - * @UniqueEntity(fields={"name", "group", "element"}) - */ +#[UniqueEntity(fields: ['name', 'group', 'element'])] +#[ORM\Entity(repositoryClass: ParameterRepository::class)] class CategoryParameter extends AbstractParameter { - public const ALLOWED_ELEMENT_CLASS = Category::class; + final public const ALLOWED_ELEMENT_CLASS = Category::class; /** * @var Category the element this para is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Category", inversedBy="parameters") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Category::class, inversedBy: 'parameters')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AbstractDBElement $element = null; } diff --git a/src/Entity/Parameters/CurrencyParameter.php b/src/Entity/Parameters/CurrencyParameter.php index cb5fad26..3540f5cb 100644 --- a/src/Entity/Parameters/CurrencyParameter.php +++ b/src/Entity/Parameters/CurrencyParameter.php @@ -41,6 +41,7 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\PriceInformations\Currency; use Doctrine\ORM\Mapping as ORM; @@ -48,18 +49,17 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** * An attachment attached to a category element. - * - * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") - * @UniqueEntity(fields={"name", "group", "element"}) */ +#[UniqueEntity(fields: ['name', 'group', 'element'])] +#[ORM\Entity(repositoryClass: ParameterRepository::class)] class CurrencyParameter extends AbstractParameter { - public const ALLOWED_ELEMENT_CLASS = Currency::class; + final public const ALLOWED_ELEMENT_CLASS = Currency::class; /** * @var Currency the element this para is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency", inversedBy="parameters") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Currency::class, inversedBy: 'parameters')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AbstractDBElement $element = null; } diff --git a/src/Entity/Parameters/FootprintParameter.php b/src/Entity/Parameters/FootprintParameter.php index cb1dd185..2a978b04 100644 --- a/src/Entity/Parameters/FootprintParameter.php +++ b/src/Entity/Parameters/FootprintParameter.php @@ -41,23 +41,22 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\Footprint; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -/** - * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") - * @UniqueEntity(fields={"name", "group", "element"}) - */ +#[UniqueEntity(fields: ['name', 'group', 'element'])] +#[ORM\Entity(repositoryClass: ParameterRepository::class)] class FootprintParameter extends AbstractParameter { - public const ALLOWED_ELEMENT_CLASS = Footprint::class; + final public const ALLOWED_ELEMENT_CLASS = Footprint::class; /** * @var Footprint the element this para is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Footprint", inversedBy="parameters") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Footprint::class, inversedBy: 'parameters')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AbstractDBElement $element = null; } diff --git a/src/Entity/Parameters/GroupParameter.php b/src/Entity/Parameters/GroupParameter.php index 81a0d39c..1bc23ea8 100644 --- a/src/Entity/Parameters/GroupParameter.php +++ b/src/Entity/Parameters/GroupParameter.php @@ -41,23 +41,22 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\UserSystem\Group; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -/** - * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") - * @UniqueEntity(fields={"name", "group", "element"}) - */ +#[UniqueEntity(fields: ['name', 'group', 'element'])] +#[ORM\Entity(repositoryClass: ParameterRepository::class)] class GroupParameter extends AbstractParameter { - public const ALLOWED_ELEMENT_CLASS = Group::class; + final public const ALLOWED_ELEMENT_CLASS = Group::class; /** * @var Group the element this para is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\Group", inversedBy="parameters") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Group::class, inversedBy: 'parameters')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AbstractDBElement $element = null; } diff --git a/src/Entity/Parameters/ManufacturerParameter.php b/src/Entity/Parameters/ManufacturerParameter.php index 56d785f2..6f33dce8 100644 --- a/src/Entity/Parameters/ManufacturerParameter.php +++ b/src/Entity/Parameters/ManufacturerParameter.php @@ -41,23 +41,22 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\Manufacturer; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -/** - * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") - * @UniqueEntity(fields={"name", "group", "element"}) - */ +#[UniqueEntity(fields: ['name', 'group', 'element'])] +#[ORM\Entity(repositoryClass: ParameterRepository::class)] class ManufacturerParameter extends AbstractParameter { - public const ALLOWED_ELEMENT_CLASS = Manufacturer::class; + final public const ALLOWED_ELEMENT_CLASS = Manufacturer::class; /** * @var Manufacturer the element this para is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Manufacturer", inversedBy="parameters") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Manufacturer::class, inversedBy: 'parameters')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AbstractDBElement $element = null; } diff --git a/src/Entity/Parameters/MeasurementUnitParameter.php b/src/Entity/Parameters/MeasurementUnitParameter.php index e4980431..7332474e 100644 --- a/src/Entity/Parameters/MeasurementUnitParameter.php +++ b/src/Entity/Parameters/MeasurementUnitParameter.php @@ -41,23 +41,22 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\MeasurementUnit; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -/** - * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") - * @UniqueEntity(fields={"name", "group", "element"}) - */ +#[UniqueEntity(fields: ['name', 'group', 'element'])] +#[ORM\Entity(repositoryClass: ParameterRepository::class)] class MeasurementUnitParameter extends AbstractParameter { - public const ALLOWED_ELEMENT_CLASS = MeasurementUnit::class; + final public const ALLOWED_ELEMENT_CLASS = MeasurementUnit::class; /** * @var MeasurementUnit the element this para is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\MeasurementUnit", inversedBy="parameters") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: MeasurementUnit::class, inversedBy: 'parameters')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AbstractDBElement $element = null; } diff --git a/src/Entity/Parameters/ParametersTrait.php b/src/Entity/Parameters/ParametersTrait.php index f8ac8e78..2ccaa763 100644 --- a/src/Entity/Parameters/ParametersTrait.php +++ b/src/Entity/Parameters/ParametersTrait.php @@ -44,20 +44,25 @@ namespace App\Entity\Parameters; use Doctrine\Common\Collections\Collection; use Symfony\Component\Validator\Constraints as Assert; +/** + * @template-covariant T of AbstractParameter + */ trait ParametersTrait { /** * Mapping done in subclasses. * * @var Collection - * @Assert\Valid() + * @phpstan-var Collection */ + #[Assert\Valid] protected Collection $parameters; /** * Return all associated specifications. + * @return Collection + * @phpstan-return Collection * - * @psalm-return Collection */ public function getParameters(): Collection { @@ -66,7 +71,7 @@ trait ParametersTrait /** * Add a new parameter information. - * + * @phpstan-param T $parameter * @return $this */ public function addParameter(AbstractParameter $parameter): self @@ -77,6 +82,9 @@ trait ParametersTrait return $this; } + /** + * @phpstan-param T $parameter + */ public function removeParameter(AbstractParameter $parameter): self { $this->parameters->removeElement($parameter); @@ -84,6 +92,10 @@ trait ParametersTrait return $this; } + /** + * @return array> + * @phpstan-return array> + */ public function getGroupedParameters(): array { $tmp = []; diff --git a/src/Entity/Parameters/PartParameter.php b/src/Entity/Parameters/PartParameter.php index a3845e55..6c150e6b 100644 --- a/src/Entity/Parameters/PartParameter.php +++ b/src/Entity/Parameters/PartParameter.php @@ -41,23 +41,25 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\Part; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; /** - * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") - * @UniqueEntity(fields={"name", "group", "element"}) + * @see \App\Tests\Entity\Parameters\PartParameterTest */ +#[UniqueEntity(fields: ['name', 'group', 'element'])] +#[ORM\Entity(repositoryClass: ParameterRepository::class)] class PartParameter extends AbstractParameter { - public const ALLOWED_ELEMENT_CLASS = Part::class; + final public const ALLOWED_ELEMENT_CLASS = Part::class; /** * @var Part the element this para is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Part", inversedBy="parameters") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'parameters')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AbstractDBElement $element = null; } diff --git a/src/Entity/Parameters/ProjectParameter.php b/src/Entity/Parameters/ProjectParameter.php index d4f8cd1a..7413ca8a 100644 --- a/src/Entity/Parameters/ProjectParameter.php +++ b/src/Entity/Parameters/ProjectParameter.php @@ -41,23 +41,22 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\ProjectSystem\Project; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -/** - * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") - * @UniqueEntity(fields={"name", "group", "element"}) - */ +#[UniqueEntity(fields: ['name', 'group', 'element'])] +#[ORM\Entity(repositoryClass: ParameterRepository::class)] class ProjectParameter extends AbstractParameter { - public const ALLOWED_ELEMENT_CLASS = Project::class; + final public const ALLOWED_ELEMENT_CLASS = Project::class; /** * @var Project the element this para is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\ProjectSystem\Project", inversedBy="parameters") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Project::class, inversedBy: 'parameters')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AbstractDBElement $element = null; } diff --git a/src/Entity/Parameters/StorelocationParameter.php b/src/Entity/Parameters/StorelocationParameter.php index 6c7d430b..098d3a5e 100644 --- a/src/Entity/Parameters/StorelocationParameter.php +++ b/src/Entity/Parameters/StorelocationParameter.php @@ -41,23 +41,22 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\Storelocation; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -/** - * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") - * @UniqueEntity(fields={"name", "group", "element"}) - */ +#[UniqueEntity(fields: ['name', 'group', 'element'])] +#[ORM\Entity(repositoryClass: ParameterRepository::class)] class StorelocationParameter extends AbstractParameter { - public const ALLOWED_ELEMENT_CLASS = Storelocation::class; + final public const ALLOWED_ELEMENT_CLASS = Storelocation::class; /** * @var Storelocation the element this para is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Storelocation", inversedBy="parameters") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Storelocation::class, inversedBy: 'parameters')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AbstractDBElement $element = null; } diff --git a/src/Entity/Parameters/SupplierParameter.php b/src/Entity/Parameters/SupplierParameter.php index 6acac705..5c87ac08 100644 --- a/src/Entity/Parameters/SupplierParameter.php +++ b/src/Entity/Parameters/SupplierParameter.php @@ -41,23 +41,22 @@ declare(strict_types=1); namespace App\Entity\Parameters; +use App\Repository\ParameterRepository; use App\Entity\Base\AbstractDBElement; use App\Entity\Parts\Supplier; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; -/** - * @ORM\Entity(repositoryClass="App\Repository\ParameterRepository") - * @UniqueEntity(fields={"name", "group", "element"}) - */ +#[UniqueEntity(fields: ['name', 'group', 'element'])] +#[ORM\Entity(repositoryClass: ParameterRepository::class)] class SupplierParameter extends AbstractParameter { - public const ALLOWED_ELEMENT_CLASS = Supplier::class; + final public const ALLOWED_ELEMENT_CLASS = Supplier::class; /** * @var Supplier the element this para is associated with - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Supplier", inversedBy="parameters") - * @ORM\JoinColumn(name="element_id", referencedColumnName="id", nullable=false, onDelete="CASCADE"). */ + #[ORM\ManyToOne(targetEntity: Supplier::class, inversedBy: 'parameters')] + #[ORM\JoinColumn(name: 'element_id', nullable: false, onDelete: 'CASCADE')] protected ?AbstractDBElement $element = null; } diff --git a/src/Entity/Parts/Category.php b/src/Entity/Parts/Category.php index 8a79d871..e5e3320e 100644 --- a/src/Entity/Parts/Category.php +++ b/src/Entity/Parts/Category.php @@ -22,6 +22,9 @@ declare(strict_types=1); namespace App\Entity\Parts; +use App\Repository\Parts\CategoryRepository; +use Doctrine\DBAL\Types\Types; +use Doctrine\Common\Collections\ArrayCollection; use App\Entity\Attachments\CategoryAttachment; use App\Entity\Base\AbstractPartsContainingDBElement; use App\Entity\Base\AbstractStructuralDBElement; @@ -32,100 +35,95 @@ use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; /** - * Class AttachmentType. + * This entity describes a category, a part can belong to, which is used to group parts by their function. * - * @ORM\Entity(repositoryClass="App\Repository\Parts\CategoryRepository") - * @ORM\Table(name="`categories`", indexes={ - * @ORM\Index(name="category_idx_name", columns={"name"}), - * @ORM\Index(name="category_idx_parent_name", columns={"parent_id", "name"}), - * }) + * @extends AbstractPartsContainingDBElement */ +#[ORM\Entity(repositoryClass: CategoryRepository::class)] +#[ORM\Table(name: '`categories`')] +#[ORM\Index(name: 'category_idx_name', columns: ['name'])] +#[ORM\Index(name: 'category_idx_parent_name', columns: ['parent_id', 'name'])] class Category extends AbstractPartsContainingDBElement { - /** - * @ORM\OneToMany(targetEntity="Category", mappedBy="parent") - * @ORM\OrderBy({"name" = "ASC"}) - * @var Collection - */ + #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $children; - /** - * @ORM\ManyToOne(targetEntity="Category", inversedBy="children") - * @ORM\JoinColumn(name="parent_id", referencedColumnName="id") - */ + #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'parent_id')] protected ?AbstractStructuralDBElement $parent = null; /** * @var string - * @ORM\Column(type="text") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::TEXT)] protected string $partname_hint = ''; /** * @var string - * @ORM\Column(type="text") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::TEXT)] protected string $partname_regex = ''; /** * @var bool - * @ORM\Column(type="boolean") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_footprints = false; /** * @var bool - * @ORM\Column(type="boolean") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_manufacturers = false; /** * @var bool - * @ORM\Column(type="boolean") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_autodatasheets = false; /** * @var bool - * @ORM\Column(type="boolean") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $disable_properties = false; /** * @var string - * @ORM\Column(type="text") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::TEXT)] protected string $default_description = ''; /** * @var string - * @ORM\Column(type="text") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::TEXT)] protected string $default_comment = ''; /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\CategoryAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"name" = "ASC"}) - * @Assert\Valid() - * @Groups({"full"}) */ + #[Assert\Valid] + #[Groups(['full'])] + #[ORM\OneToMany(targetEntity: CategoryAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; /** @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Parameters\CategoryParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) - * @Assert\Valid() - * @Groups({"full"}) */ + #[Assert\Valid] + #[Groups(['full'])] + #[ORM\OneToMany(targetEntity: CategoryParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; public function getPartnameHint(): string @@ -133,9 +131,6 @@ class Category extends AbstractPartsContainingDBElement return $this->partname_hint; } - /** - * @return Category - */ public function setPartnameHint(string $partname_hint): self { $this->partname_hint = $partname_hint; @@ -148,9 +143,6 @@ class Category extends AbstractPartsContainingDBElement return $this->partname_regex; } - /** - * @return Category - */ public function setPartnameRegex(string $partname_regex): self { $this->partname_regex = $partname_regex; @@ -163,9 +155,6 @@ class Category extends AbstractPartsContainingDBElement return $this->disable_footprints; } - /** - * @return Category - */ public function setDisableFootprints(bool $disable_footprints): self { $this->disable_footprints = $disable_footprints; @@ -178,9 +167,6 @@ class Category extends AbstractPartsContainingDBElement return $this->disable_manufacturers; } - /** - * @return Category - */ public function setDisableManufacturers(bool $disable_manufacturers): self { $this->disable_manufacturers = $disable_manufacturers; @@ -193,9 +179,6 @@ class Category extends AbstractPartsContainingDBElement return $this->disable_autodatasheets; } - /** - * @return Category - */ public function setDisableAutodatasheets(bool $disable_autodatasheets): self { $this->disable_autodatasheets = $disable_autodatasheets; @@ -208,9 +191,6 @@ class Category extends AbstractPartsContainingDBElement return $this->disable_properties; } - /** - * @return Category - */ public function setDisableProperties(bool $disable_properties): self { $this->disable_properties = $disable_properties; @@ -223,9 +203,6 @@ class Category extends AbstractPartsContainingDBElement return $this->default_description; } - /** - * @return Category - */ public function setDefaultDescription(string $default_description): self { $this->default_description = $default_description; @@ -238,13 +215,17 @@ class Category extends AbstractPartsContainingDBElement return $this->default_comment; } - /** - * @return Category - */ public function setDefaultComment(string $default_comment): self { $this->default_comment = $default_comment; return $this; } + public function __construct() + { + parent::__construct(); + $this->children = new ArrayCollection(); + $this->attachments = new ArrayCollection(); + $this->parameters = new ArrayCollection(); + } } diff --git a/src/Entity/Parts/Footprint.php b/src/Entity/Parts/Footprint.php index 09a4f4d0..8138f967 100644 --- a/src/Entity/Parts/Footprint.php +++ b/src/Entity/Parts/Footprint.php @@ -22,6 +22,9 @@ declare(strict_types=1); namespace App\Entity\Parts; +use App\Repository\Parts\FootprintRepository; +use App\Entity\Base\AbstractStructuralDBElement; +use Doctrine\Common\Collections\ArrayCollection; use App\Entity\Attachments\FootprintAttachment; use App\Entity\Base\AbstractPartsContainingDBElement; use App\Entity\Parameters\FootprintParameter; @@ -30,49 +33,44 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** - * Class Footprint. + * This entity represents a footprint of a part (its physical dimensions and shape). * - * @ORM\Entity(repositoryClass="App\Repository\Parts\FootprintRepository") - * @ORM\Table("`footprints`", indexes={ - * @ORM\Index(name="footprint_idx_name", columns={"name"}), - * @ORM\Index(name="footprint_idx_parent_name", columns={"parent_id", "name"}), - * }) + * @extends AbstractPartsContainingDBElement */ +#[ORM\Entity(repositoryClass: FootprintRepository::class)] +#[ORM\Table('`footprints`')] +#[ORM\Index(name: 'footprint_idx_name', columns: ['name'])] +#[ORM\Index(name: 'footprint_idx_parent_name', columns: ['parent_id', 'name'])] class Footprint extends AbstractPartsContainingDBElement { - /** - * @ORM\ManyToOne(targetEntity="Footprint", inversedBy="children") - * @ORM\JoinColumn(name="parent_id", referencedColumnName="id") - */ - protected ?\App\Entity\Base\AbstractStructuralDBElement $parent; + #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'parent_id')] + protected ?AbstractStructuralDBElement $parent = null; - /** - * @ORM\OneToMany(targetEntity="Footprint", mappedBy="parent") - * @ORM\OrderBy({"name" = "ASC"}) - * @var Collection - */ + #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $children; /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\FootprintAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: FootprintAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; /** * @var FootprintAttachment|null - * @ORM\ManyToOne(targetEntity="App\Entity\Attachments\FootprintAttachment") - * @ORM\JoinColumn(name="id_footprint_3d", referencedColumnName="id") */ + #[ORM\ManyToOne(targetEntity: FootprintAttachment::class)] + #[ORM\JoinColumn(name: 'id_footprint_3d')] protected ?FootprintAttachment $footprint_3d = null; /** @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Parameters\FootprintParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: FootprintParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; /**************************************** @@ -92,13 +90,10 @@ class Footprint extends AbstractPartsContainingDBElement * Setters * *********************************************************************************/ - /** * Sets the 3D Model associated with this footprint. * * @param FootprintAttachment|null $new_attachment The new 3D Model - * - * @return Footprint */ public function setFootprint3d(?FootprintAttachment $new_attachment): self { @@ -106,4 +101,11 @@ class Footprint extends AbstractPartsContainingDBElement return $this; } + public function __construct() + { + parent::__construct(); + $this->children = new ArrayCollection(); + $this->attachments = new ArrayCollection(); + $this->parameters = new ArrayCollection(); + } } diff --git a/src/Entity/Parts/Manufacturer.php b/src/Entity/Parts/Manufacturer.php index 74d0e308..b6768e4a 100644 --- a/src/Entity/Parts/Manufacturer.php +++ b/src/Entity/Parts/Manufacturer.php @@ -22,6 +22,9 @@ declare(strict_types=1); namespace App\Entity\Parts; +use App\Repository\Parts\ManufacturerRepository; +use App\Entity\Base\AbstractStructuralDBElement; +use Doctrine\Common\Collections\ArrayCollection; use App\Entity\Attachments\ManufacturerAttachment; use App\Entity\Base\AbstractCompany; use App\Entity\Parameters\ManufacturerParameter; @@ -30,41 +33,43 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** - * Class Manufacturer. + * This entity represents a manufacturer of a part (The company that produces the part). * - * @ORM\Entity(repositoryClass="App\Repository\Parts\ManufacturerRepository") - * @ORM\Table("`manufacturers`", indexes={ - * @ORM\Index(name="manufacturer_name", columns={"name"}), - * @ORM\Index(name="manufacturer_idx_parent_name", columns={"parent_id", "name"}), - * }) + * @extends AbstractCompany */ +#[ORM\Entity(repositoryClass: ManufacturerRepository::class)] +#[ORM\Table('`manufacturers`')] +#[ORM\Index(name: 'manufacturer_name', columns: ['name'])] +#[ORM\Index(name: 'manufacturer_idx_parent_name', columns: ['parent_id', 'name'])] class Manufacturer extends AbstractCompany { - /** - * @ORM\ManyToOne(targetEntity="Manufacturer", inversedBy="children") - * @ORM\JoinColumn(name="parent_id", referencedColumnName="id") - */ - protected ?\App\Entity\Base\AbstractStructuralDBElement $parent; + #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'parent_id')] + protected ?AbstractStructuralDBElement $parent = null; - /** - * @ORM\OneToMany(targetEntity="Manufacturer", mappedBy="parent") - * @ORM\OrderBy({"name" = "ASC"}) - * @var Collection - */ + #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $children; /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\ManufacturerAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: ManufacturerAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; /** @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Parameters\ManufacturerParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: ManufacturerParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; + public function __construct() + { + parent::__construct(); + $this->children = new ArrayCollection(); + $this->attachments = new ArrayCollection(); + $this->parameters = new ArrayCollection(); + } } diff --git a/src/Entity/Parts/MeasurementUnit.php b/src/Entity/Parts/MeasurementUnit.php index b33d47fc..054056a8 100644 --- a/src/Entity/Parts/MeasurementUnit.php +++ b/src/Entity/Parts/MeasurementUnit.php @@ -22,6 +22,10 @@ declare(strict_types=1); namespace App\Entity\Parts; +use App\Repository\Parts\MeasurementUnitRepository; +use Doctrine\DBAL\Types\Types; +use App\Entity\Base\AbstractStructuralDBElement; +use Doctrine\Common\Collections\ArrayCollection; use App\Entity\Attachments\MeasurementUnitAttachment; use App\Entity\Base\AbstractPartsContainingDBElement; use App\Entity\Parameters\MeasurementUnitParameter; @@ -35,67 +39,62 @@ use Symfony\Component\Validator\Constraints as Assert; * This unit represents the unit in which the amount of parts in stock are measured. * This could be something like N, grams, meters, etc... * - * @ORM\Entity(repositoryClass="App\Repository\Parts\MeasurementUnitRepository") - * @ORM\Table(name="`measurement_units`", indexes={ - * @ORM\Index(name="unit_idx_name", columns={"name"}), - * @ORM\Index(name="unit_idx_parent_name", columns={"parent_id", "name"}), - * }) - * @UniqueEntity("unit") + * @extends AbstractPartsContainingDBElement */ +#[UniqueEntity('unit')] +#[ORM\Entity(repositoryClass: MeasurementUnitRepository::class)] +#[ORM\Table(name: '`measurement_units`')] +#[ORM\Index(name: 'unit_idx_name', columns: ['name'])] +#[ORM\Index(name: 'unit_idx_parent_name', columns: ['parent_id', 'name'])] class MeasurementUnit extends AbstractPartsContainingDBElement { /** * @var string The unit symbol that should be used for the Unit. This could be something like "", g (for grams) * or m (for meters). - * @ORM\Column(type="string", name="unit", nullable=true) - * @Assert\Length(max=10) - * @Groups({"extended", "full", "import"}) */ + #[Assert\Length(max: 10)] + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::STRING, name: 'unit', nullable: true)] protected ?string $unit = null; /** * @var bool Determines if the amount value associated with this unit should be treated as integer. * Set to false, to measure continuous sizes likes masses or lengths. - * @ORM\Column(type="boolean", name="is_integer") - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN, name: 'is_integer')] protected bool $is_integer = false; /** * @var bool Determines if the unit can be used with SI Prefixes (kilo, giga, milli, etc.). * Useful for sizes like meters. For this the unit must be set - * @ORM\Column(type="boolean", name="use_si_prefix") - * @Assert\Expression("this.isUseSIPrefix() == false or this.getUnit() != null", message="validator.measurement_unit.use_si_prefix_needs_unit") - * @Groups({"full", "import"}) */ + #[Assert\Expression('this.isUseSIPrefix() == false or this.getUnit() != null', message: 'validator.measurement_unit.use_si_prefix_needs_unit')] + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN, name: 'use_si_prefix')] protected bool $use_si_prefix = false; - /** - * @ORM\OneToMany(targetEntity="MeasurementUnit", mappedBy="parent", cascade={"persist"}) - * @ORM\OrderBy({"name" = "ASC"}) - * @var Collection - */ + #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent', cascade: ['persist'])] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $children; - /** - * @ORM\ManyToOne(targetEntity="MeasurementUnit", inversedBy="children") - * @ORM\JoinColumn(name="parent_id", referencedColumnName="id") - */ - protected ?\App\Entity\Base\AbstractStructuralDBElement $parent; + #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'parent_id')] + protected ?AbstractStructuralDBElement $parent = null; /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\MeasurementUnitAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: MeasurementUnitAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; /** @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Parameters\MeasurementUnitParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: MeasurementUnitParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; /** @@ -106,11 +105,6 @@ class MeasurementUnit extends AbstractPartsContainingDBElement return $this->unit; } - /** - * @param string|null $unit - * - * @return MeasurementUnit - */ public function setUnit(?string $unit): self { $this->unit = $unit; @@ -123,9 +117,6 @@ class MeasurementUnit extends AbstractPartsContainingDBElement return $this->is_integer; } - /** - * @return MeasurementUnit - */ public function setIsInteger(bool $isInteger): self { $this->is_integer = $isInteger; @@ -138,13 +129,17 @@ class MeasurementUnit extends AbstractPartsContainingDBElement return $this->use_si_prefix; } - /** - * @return MeasurementUnit - */ public function setUseSIPrefix(bool $usesSIPrefixes): self { $this->use_si_prefix = $usesSIPrefixes; return $this; } + public function __construct() + { + parent::__construct(); + $this->children = new ArrayCollection(); + $this->attachments = new ArrayCollection(); + $this->parameters = new ArrayCollection(); + } } diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index d9b7e601..9279ec11 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\Parts; +use App\Repository\PartRepository; +use Doctrine\DBAL\Types\Types; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\PartAttachment; @@ -47,15 +49,16 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; * * The class properties are split over various traits in directory PartTraits. * Otherwise, this class would be too big, to be maintained. - * - * @ORM\Entity(repositoryClass="App\Repository\PartRepository") - * @ORM\Table("`parts`", indexes={ - * @ORM\Index(name="parts_idx_datet_name_last_id_needs", columns={"datetime_added", "name", "last_modified", "id", "needs_review"}), - * @ORM\Index(name="parts_idx_name", columns={"name"}), - * @ORM\Index(name="parts_idx_ipn", columns={"ipn"}), - * }) - * @UniqueEntity(fields={"ipn"}, message="part.ipn.must_be_unique") + * @see \App\Tests\Entity\Parts\PartTest + * @extends AttachmentContainingDBElement + * @template-use ParametersTrait */ +#[UniqueEntity(fields: ['ipn'], message: 'part.ipn.must_be_unique')] +#[ORM\Entity(repositoryClass: PartRepository::class)] +#[ORM\Table('`parts`')] +#[ORM\Index(name: 'parts_idx_datet_name_last_id_needs', columns: ['datetime_added', 'name', 'last_modified', 'id', 'needs_review'])] +#[ORM\Index(name: 'parts_idx_name', columns: ['name'])] +#[ORM\Index(name: 'parts_idx_ipn', columns: ['ipn'])] class Part extends AttachmentContainingDBElement { use AdvancedPropertyTrait; @@ -68,54 +71,44 @@ class Part extends AttachmentContainingDBElement use ProjectTrait; /** @var Collection - * @Assert\Valid() - * @ORM\OneToMany(targetEntity="App\Entity\Parameters\PartParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) - * @Groups({"full"}) */ + #[Assert\Valid] + #[Groups(['full'])] + #[ORM\OneToMany(targetEntity: PartParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; - /** - * @ORM\Column(type="datetime", name="datetime_added", options={"default":"CURRENT_TIMESTAMP"}) - */ - protected ?DateTime $addedDate = null; /** ************************************************************* * Overridden properties * (They are defined here and not in a trait, to avoid conflicts). ****************************************************************/ - /** * @var string The name of this part - * @ORM\Column(type="string") */ + #[ORM\Column(type: Types::STRING)] protected string $name = ''; /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\PartAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"name" = "ASC"}) - * @Assert\Valid() - * @Groups({"full"}) */ + #[Assert\Valid] + #[Groups(['full'])] + #[ORM\OneToMany(targetEntity: PartAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; - /** - * @var DateTime|null the date when this element was modified the last time - * @ORM\Column(type="datetime", name="last_modified", options={"default":"CURRENT_TIMESTAMP"}) - */ - protected ?DateTime $lastModified = null; - /** * @var Attachment|null - * @ORM\ManyToOne(targetEntity="App\Entity\Attachments\Attachment") - * @ORM\JoinColumn(name="id_preview_attachment", referencedColumnName="id", onDelete="SET NULL", nullable=true) - * @Assert\Expression("value == null or value.isPicture()", message="part.master_attachment.must_be_picture") */ + #[Assert\Expression('value == null or value.isPicture()', message: 'part.master_attachment.must_be_picture')] + #[ORM\ManyToOne(targetEntity: Attachment::class)] + #[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')] protected ?Attachment $master_picture_attachment = null; public function __construct() { + $this->attachments = new ArrayCollection(); parent::__construct(); $this->partLots = new ArrayCollection(); $this->orderdetails = new ArrayCollection(); @@ -150,21 +143,17 @@ class Part extends AttachmentContainingDBElement parent::__clone(); } - /** - * @Assert\Callback - */ - public function validate(ExecutionContextInterface $context, $payload) + #[Assert\Callback] + public function validate(ExecutionContextInterface $context, $payload): void { //Ensure that the part name fullfills the regex of the category - if ($this->category) { + if ($this->category instanceof Category) { $regex = $this->category->getPartnameRegex(); - if (!empty($regex)) { - if (!preg_match($regex, $this->name)) { - $context->buildViolation('part.name.must_match_category_regex') - ->atPath('name') - ->setParameter('%regex%', $regex) - ->addViolation(); - } + if ($regex !== '' && !preg_match($regex, $this->name)) { + $context->buildViolation('part.name.must_match_category_regex') + ->atPath('name') + ->setParameter('%regex%', $regex) + ->addViolation(); } } } diff --git a/src/Entity/Parts/PartLot.php b/src/Entity/Parts/PartLot.php index cd69651f..c54511f6 100644 --- a/src/Entity/Parts/PartLot.php +++ b/src/Entity/Parts/PartLot.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts; +use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\TimestampTrait; use App\Entity\Contracts\NamedElementInterface; @@ -40,84 +41,84 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; * This entity describes a lot where parts can be stored. * It is the connection between a part and its store locations. * - * @ORM\Entity() - * @ORM\Table(name="part_lots", indexes={ - * @ORM\Index(name="part_lots_idx_instock_un_expiration_id_part", columns={"instock_unknown", "expiration_date", "id_part"}), - * @ORM\Index(name="part_lots_idx_needs_refill", columns={"needs_refill"}), - * }) - * @ORM\HasLifecycleCallbacks() - * @ValidPartLot() + * @see \App\Tests\Entity\Parts\PartLotTest */ +#[ORM\Entity] +#[ORM\HasLifecycleCallbacks] +#[ORM\Table(name: 'part_lots')] +#[ORM\Index(name: 'part_lots_idx_instock_un_expiration_id_part', columns: ['instock_unknown', 'expiration_date', 'id_part'])] +#[ORM\Index(name: 'part_lots_idx_needs_refill', columns: ['needs_refill'])] +#[ValidPartLot] class PartLot extends AbstractDBElement implements TimeStampableInterface, NamedElementInterface { use TimestampTrait; /** * @var string A short description about this lot, shown in table - * @ORM\Column(type="text") - * @Groups({"simple", "extended", "full", "import"}) */ + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\Column(type: Types::TEXT)] protected string $description = ''; /** * @var string a comment stored with this lot - * @ORM\Column(type="text") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::TEXT)] protected string $comment = ''; /** - * @var ?DateTime Set a time until when the lot must be used. + * @var \DateTimeInterface|null Set a time until when the lot must be used. * Set to null, if the lot can be used indefinitely. - * @ORM\Column(type="datetime", name="expiration_date", nullable=true) - * @Groups({"extended", "full", "import"}) */ - protected ?DateTime $expiration_date = null; + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::DATETIME_MUTABLE, name: 'expiration_date', nullable: true)] + protected ?\DateTimeInterface $expiration_date = null; /** * @var Storelocation|null The storelocation of this lot - * @ORM\ManyToOne(targetEntity="Storelocation") - * @ORM\JoinColumn(name="id_store_location", referencedColumnName="id", nullable=true) - * @Selectable() - * @Groups({"simple", "extended", "full", "import"}) */ + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\ManyToOne(targetEntity: Storelocation::class)] + #[ORM\JoinColumn(name: 'id_store_location')] + #[Selectable()] protected ?Storelocation $storage_location = null; /** * @var bool If this is set to true, the instock amount is marked as not known - * @ORM\Column(type="boolean") - * @Groups({"simple", "extended", "full", "import"}) */ + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $instock_unknown = false; /** * @var float For continuous sizes (length, volume, etc.) the instock is saved here. - * @ORM\Column(type="float") - * @Assert\PositiveOrZero() - * @Groups({"simple", "extended", "full", "import"}) */ + #[Assert\PositiveOrZero] + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\Column(type: Types::FLOAT)] protected float $amount = 0.0; /** * @var bool determines if this lot was manually marked for refilling - * @ORM\Column(type="boolean") - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $needs_refill = false; /** - * @var Part The part that is stored in this lot - * @ORM\ManyToOne(targetEntity="Part", inversedBy="partLots") - * @ORM\JoinColumn(name="id_part", referencedColumnName="id", nullable=false, onDelete="CASCADE") - * @Assert\NotNull() + * @var Part|null The part that is stored in this lot */ - protected Part $part; + #[Assert\NotNull] + #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'partLots')] + #[ORM\JoinColumn(name: 'id_part', nullable: false, onDelete: 'CASCADE')] + protected ?Part $part = null; /** * @var User|null The owner of this part lot - * @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User") - * @ORM\JoinColumn(name="id_owner", referencedColumnName="id", nullable=true, onDelete="SET NULL") */ + #[ORM\ManyToOne(targetEntity: User::class)] + #[ORM\JoinColumn(name: 'id_owner', onDelete: 'SET NULL')] protected ?User $owner = null; public function __clone() @@ -138,7 +139,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named */ public function isExpired(): ?bool { - if (null === $this->expiration_date) { + if (!$this->expiration_date instanceof \DateTimeInterface) { return null; } @@ -156,8 +157,6 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Sets the description of the part lot. - * - * @return PartLot */ public function setDescription(string $description): self { @@ -176,8 +175,6 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Sets the comment for this part lot. - * - * @return PartLot */ public function setComment(string $comment): self { @@ -189,7 +186,7 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Gets the expiration date for the part lot. Returns null, if no expiration date was set. */ - public function getExpirationDate(): ?DateTime + public function getExpirationDate(): ?\DateTimeInterface { return $this->expiration_date; } @@ -197,11 +194,9 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Sets the expiration date for the part lot. Set to null, if the part lot does not expire. * - * @param DateTime|null $expiration_date * - * @return PartLot */ - public function setExpirationDate(?DateTime $expiration_date): self + public function setExpirationDate(?\DateTimeInterface $expiration_date): self { $this->expiration_date = $expiration_date; @@ -220,8 +215,6 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Sets the storage location, where this part lot is stored. - * - * @return PartLot */ public function setStorageLocation(?Storelocation $storage_location): self { @@ -233,15 +226,13 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Return the part that is stored in this part lot. */ - public function getPart(): Part + public function getPart(): ?Part { return $this->part; } /** * Sets the part that is stored in this part lot. - * - * @return PartLot */ public function setPart(Part $part): self { @@ -260,8 +251,6 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Set the unknown instock status of this part lot. - * - * @return PartLot */ public function setInstockUnknown(bool $instock_unknown): self { @@ -303,9 +292,6 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named return $this->needs_refill; } - /** - * @return PartLot - */ public function setNeedsRefill(bool $needs_refill): self { $this->needs_refill = $needs_refill; @@ -315,7 +301,6 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Returns the owner of this part lot. - * @return User|null */ public function getOwner(): ?User { @@ -324,8 +309,6 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named /** * Sets the owner of this part lot. - * @param User|null $owner - * @return PartLot */ public function setOwner(?User $owner): PartLot { @@ -338,10 +321,8 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named return $this->description; } - /** - * @Assert\Callback - */ - public function validate(ExecutionContextInterface $context, $payload) + #[Assert\Callback] + public function validate(ExecutionContextInterface $context, $payload): void { //Ensure that the owner is not the anonymous user if ($this->getOwner() && $this->getOwner()->isAnonymousUser()) { @@ -352,14 +333,12 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named //When the storage location sets the owner must match, the part lot owner must match the storage location owner if ($this->getStorageLocation() && $this->getStorageLocation()->isPartOwnerMustMatch() - && $this->getStorageLocation()->getOwner() && $this->getOwner()) { - if ($this->getOwner() !== $this->getStorageLocation()->getOwner() - && $this->owner->getID() !== $this->getStorageLocation()->getOwner()->getID()) { - $context->buildViolation('validator.part_lot.owner_must_match_storage_location_owner') - ->setParameter('%owner_name%', $this->getStorageLocation()->getOwner()->getFullName(true)) - ->atPath('owner') - ->addViolation(); - } + && $this->getStorageLocation()->getOwner() && $this->getOwner() && ($this->getOwner() !== $this->getStorageLocation()->getOwner() + && $this->owner->getID() !== $this->getStorageLocation()->getOwner()->getID())) { + $context->buildViolation('validator.part_lot.owner_must_match_storage_location_owner') + ->setParameter('%owner_name%', $this->getStorageLocation()->getOwner()->getFullName(true)) + ->atPath('owner') + ->addViolation(); } } } diff --git a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php index 50935fd3..633bf9d0 100644 --- a/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php +++ b/src/Entity/Parts/PartTraits/AdvancedPropertyTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; +use Doctrine\DBAL\Types\Types; use App\Entity\Parts\Part; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Groups; @@ -34,32 +35,32 @@ trait AdvancedPropertyTrait { /** * @var bool Determines if this part entry needs review (for example, because it is work in progress) - * @ORM\Column(type="boolean") - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $needs_review = false; /** * @var string a comma separated list of tags, associated with the part - * @ORM\Column(type="text") - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::TEXT)] protected string $tags = ''; /** * @var float|null how much a single part unit weighs in grams - * @ORM\Column(type="float", nullable=true) - * @Assert\PositiveOrZero() - * @Groups({"extended", "full", "import"}) */ + #[Assert\PositiveOrZero] + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::FLOAT, nullable: true)] protected ?float $mass = null; /** * @var string|null The internal part number of the part - * @ORM\Column(type="string", length=100, nullable=true, unique=true) - * @Assert\Length(max="100") - * @Groups({"extended", "full", "import"}) */ + #[Assert\Length(max: 100)] + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::STRING, length: 100, nullable: true, unique: true)] protected ?string $ipn = null; /** @@ -142,7 +143,6 @@ trait AdvancedPropertyTrait /** * Sets the internal part number of the part * @param string $ipn The new IPN of the part - * @return Part */ public function setIpn(?string $ipn): Part { diff --git a/src/Entity/Parts/PartTraits/BasicPropertyTrait.php b/src/Entity/Parts/PartTraits/BasicPropertyTrait.php index f675dc72..b0f593d6 100644 --- a/src/Entity/Parts/PartTraits/BasicPropertyTrait.php +++ b/src/Entity/Parts/PartTraits/BasicPropertyTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; +use Doctrine\DBAL\Types\Types; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Validator\Constraints\Selectable; @@ -33,49 +34,49 @@ trait BasicPropertyTrait { /** * @var string A text describing what this part does - * @ORM\Column(type="text") - * @Groups({"simple", "extended", "full", "import"}) */ + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\Column(type: Types::TEXT)] protected string $description = ''; /** * @var string A comment/note related to this part - * @ORM\Column(type="text") - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::TEXT)] protected string $comment = ''; /** * @var bool Kept for compatibility (it is not used now, and I don't think it was used in old versions) - * @ORM\Column(type="boolean") */ + #[ORM\Column(type: Types::BOOLEAN)] protected bool $visible = true; /** * @var bool true, if the part is marked as favorite - * @ORM\Column(type="boolean") - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $favorite = false; /** * @var Category|null The category this part belongs too (e.g. Resistors). Use tags, for more complex grouping. * Every part must have a category. - * @ORM\ManyToOne(targetEntity="Category") - * @ORM\JoinColumn(name="id_category", referencedColumnName="id", nullable=false) - * @Selectable() - * @Assert\NotNull(message="validator.select_valid_category") - * @Groups({"simple", "extended", "full", "import"}) */ + #[Assert\NotNull(message: 'validator.select_valid_category')] + #[Selectable()] + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\ManyToOne(targetEntity: Category::class)] + #[ORM\JoinColumn(name: 'id_category', nullable: false)] protected ?Category $category = null; /** * @var Footprint|null The footprint of this part (e.g. DIP8) - * @ORM\ManyToOne(targetEntity="Footprint") - * @ORM\JoinColumn(name="id_footprint", referencedColumnName="id") - * @Selectable() - * @Groups({"simple", "extended", "full", "import"}) */ + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\ManyToOne(targetEntity: Footprint::class)] + #[ORM\JoinColumn(name: 'id_footprint')] + #[Selectable()] protected ?Footprint $footprint = null; /** diff --git a/src/Entity/Parts/PartTraits/InstockTrait.php b/src/Entity/Parts/PartTraits/InstockTrait.php index 8ab300b5..068173a7 100644 --- a/src/Entity/Parts/PartTraits/InstockTrait.php +++ b/src/Entity/Parts/PartTraits/InstockTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; +use Doctrine\DBAL\Types\Types; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\PartLot; use Doctrine\Common\Collections\Collection; @@ -36,34 +37,33 @@ trait InstockTrait { /** * @var Collection|PartLot[] A list of part lots where this part is stored - * @ORM\OneToMany(targetEntity="PartLot", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true) - * @Assert\Valid() - * @ORM\OrderBy({"amount" = "DESC"}) - * @Groups({"extended", "full", "import"}) */ - protected $partLots; + #[Assert\Valid] + #[Groups(['extended', 'full', 'import'])] + #[ORM\OneToMany(targetEntity: PartLot::class, mappedBy: 'part', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['amount' => 'DESC'])] + protected Collection $partLots; /** * @var float The minimum amount of the part that has to be instock, otherwise more is ordered. * Given in the partUnit. - * @ORM\Column(type="float") - * @Assert\PositiveOrZero() - * @Groups({"extended", "full", "import"}) */ + #[Assert\PositiveOrZero] + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::FLOAT)] protected float $minamount = 0; /** * @var ?MeasurementUnit the unit in which the part's amount is measured - * @ORM\ManyToOne(targetEntity="MeasurementUnit") - * @ORM\JoinColumn(name="id_part_unit", referencedColumnName="id", nullable=true) - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\ManyToOne(targetEntity: MeasurementUnit::class)] + #[ORM\JoinColumn(name: 'id_part_unit')] protected ?MeasurementUnit $partUnit = null; /** * Get all part lots where this part is stored. - * - * @return PartLot[]|Collection + * @phpstan-return Collection */ public function getPartLots(): Collection { @@ -153,7 +153,6 @@ trait InstockTrait /** * Returns true, if the total instock amount of this part is less than the minimum amount. - * @return bool */ public function isNotEnoughInstock(): bool { @@ -188,7 +187,7 @@ trait InstockTrait $sum = 0; foreach ($this->getPartLots() as $lot) { //Don't use the in stock value, if it is unknown - if ($lot->isInstockUnknown() || $lot->isExpired() ?? false) { + if ($lot->isInstockUnknown() || ($lot->isExpired() ?? false)) { continue; } @@ -204,7 +203,6 @@ trait InstockTrait /** * Returns the summed amount of all part lots that are expired. If no part lots are expired 0 is returned. - * @return float */ public function getExpiredAmountSum(): float { diff --git a/src/Entity/Parts/PartTraits/ManufacturerTrait.php b/src/Entity/Parts/PartTraits/ManufacturerTrait.php index de4bd940..81ab8ac9 100644 --- a/src/Entity/Parts/PartTraits/ManufacturerTrait.php +++ b/src/Entity/Parts/PartTraits/ManufacturerTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; +use Doctrine\DBAL\Types\Types; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Part; use App\Validator\Constraints\Selectable; @@ -36,34 +37,34 @@ trait ManufacturerTrait { /** * @var Manufacturer|null The manufacturer of this part - * @ORM\ManyToOne(targetEntity="Manufacturer") - * @ORM\JoinColumn(name="id_manufacturer", referencedColumnName="id") - * @Selectable() - * @Groups({"simple","extended", "full", "import"}) */ + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\ManyToOne(targetEntity: Manufacturer::class)] + #[ORM\JoinColumn(name: 'id_manufacturer')] + #[Selectable()] protected ?Manufacturer $manufacturer = null; /** * @var string the url to the part on the manufacturer's homepage - * @ORM\Column(type="string") - * @Assert\Url() - * @Groups({"full", "import"}) */ + #[Assert\Url] + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::STRING)] protected string $manufacturer_product_url = ''; /** * @var string The product number used by the manufacturer. If this is set to "", the name field is used. - * @ORM\Column(type="string") - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::STRING)] protected string $manufacturer_product_number = ''; /** * @var string|null The production status of this part. Can be one of the specified ones. - * @ORM\Column(type="string", length=255, nullable=true) - * @Assert\Choice({"announced", "active", "nrfnd", "eol", "discontinued", ""}) - * @Groups({"extended", "full", "import"}) */ + #[Assert\Choice(['announced', 'active', 'nrfnd', 'eol', 'discontinued', ''])] + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] protected ?string $manufacturing_status = ''; /** diff --git a/src/Entity/Parts/PartTraits/OrderTrait.php b/src/Entity/Parts/PartTraits/OrderTrait.php index d92ab762..a442298c 100644 --- a/src/Entity/Parts/PartTraits/OrderTrait.php +++ b/src/Entity/Parts/PartTraits/OrderTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\Parts\PartTraits; +use Doctrine\DBAL\Types\Types; use App\Entity\PriceInformations\Orderdetail; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; @@ -35,37 +36,37 @@ use Doctrine\ORM\Mapping as ORM; trait OrderTrait { /** - * @var Orderdetail[]|Collection the details about how and where you can order this part - * @ORM\OneToMany(targetEntity="App\Entity\PriceInformations\Orderdetail", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true) - * @Assert\Valid() - * @ORM\OrderBy({"supplierpartnr" = "ASC"}) - * @Groups({"extended", "full", "import"}) + * @var Collection the details about how and where you can order this part */ - protected $orderdetails; + #[Assert\Valid] + #[Groups(['extended', 'full', 'import'])] + #[ORM\OneToMany(targetEntity: Orderdetail::class, mappedBy: 'part', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['supplierpartnr' => 'ASC'])] + protected Collection $orderdetails; /** * @var int - * @ORM\Column(type="integer") */ + #[ORM\Column(type: Types::INTEGER)] protected int $order_quantity = 0; /** * @var bool - * @ORM\Column(type="boolean") */ + #[ORM\Column(type: Types::BOOLEAN)] protected bool $manual_order = false; /** * @var Orderdetail|null - * @ORM\OneToOne(targetEntity="App\Entity\PriceInformations\Orderdetail") - * @ORM\JoinColumn(name="order_orderdetails_id", referencedColumnName="id") */ + #[ORM\OneToOne(targetEntity: Orderdetail::class)] + #[ORM\JoinColumn(name: 'order_orderdetails_id')] protected ?Orderdetail $order_orderdetail = null; /** * Get the selected order orderdetails of this part. * - * @return Orderdetail the selected order orderdetails + * @return Orderdetail|null the selected order orderdetails */ public function getOrderOrderdetails(): ?Orderdetail { @@ -97,7 +98,7 @@ trait OrderTrait * * @param bool $hide_obsolete If true, obsolete orderdetails will NOT be returned * - * @return Collection|Orderdetail[] * all orderdetails as a one-dimensional array of Orderdetails objects + * @return Collection * all orderdetails as a one-dimensional array of Orderdetails objects * (empty array if there are no ones) * * the array is sorted by the suppliers names / minimum order quantity */ @@ -105,9 +106,7 @@ trait OrderTrait { //If needed hide the obsolete entries if ($hide_obsolete) { - return $this->orderdetails->filter(function (Orderdetail $orderdetail) { - return ! $orderdetail->getObsolete(); - }); + return $this->orderdetails->filter(fn(Orderdetail $orderdetail) => ! $orderdetail->getObsolete()); } return $this->orderdetails; diff --git a/src/Entity/Parts/PartTraits/ProjectTrait.php b/src/Entity/Parts/PartTraits/ProjectTrait.php index e0d646d2..51208b6a 100644 --- a/src/Entity/Parts/PartTraits/ProjectTrait.php +++ b/src/Entity/Parts/PartTraits/ProjectTrait.php @@ -1,30 +1,37 @@ $project_bom_entries - * @ORM\OneToMany(targetEntity="App\Entity\ProjectSystem\ProjectBOMEntry", mappedBy="part", cascade={"remove"}, orphanRemoval=true) + * @var Collection $project_bom_entries */ - protected $project_bom_entries = []; + /** + * @var Collection $project_bom_entries + */ + #[ORM\OneToMany(targetEntity: ProjectBOMEntry::class, mappedBy: 'part', cascade: ['remove'], orphanRemoval: true)] + protected Collection $project_bom_entries; /** * @var Project|null If a project is set here, then this part is special and represents the builds of a project. - * @ORM\OneToOne(targetEntity="App\Entity\ProjectSystem\Project", inversedBy="build_part") - * @ORM\JoinColumn(nullable=true) */ + #[ORM\OneToOne(targetEntity: Project::class, inversedBy: 'build_part')] + #[ORM\JoinColumn] protected ?Project $built_project = null; /** - * Returns all ProjectBOMEntries that use this part. - * @return Collection|ProjectBOMEntry[] + * Returns all ProjectBOMEntries that use this part. + * + * @phpstan-return Collection */ public function getProjectBomEntries(): Collection { @@ -42,7 +49,6 @@ trait ProjectTrait /** * Returns the project that this part represents the builds of, or null if it doesn't - * @return Project|null */ public function getBuiltProject(): ?Project { @@ -78,4 +84,4 @@ trait ProjectTrait return $projects; } -} \ No newline at end of file +} diff --git a/src/Entity/Parts/Storelocation.php b/src/Entity/Parts/Storelocation.php index 03c5951f..08c4911b 100644 --- a/src/Entity/Parts/Storelocation.php +++ b/src/Entity/Parts/Storelocation.php @@ -22,6 +22,9 @@ declare(strict_types=1); namespace App\Entity\Parts; +use App\Repository\Parts\StorelocationRepository; +use Doctrine\DBAL\Types\Types; +use Doctrine\Common\Collections\ArrayCollection; use App\Entity\Attachments\StorelocationAttachment; use App\Entity\Base\AbstractPartsContainingDBElement; use App\Entity\Base\AbstractStructuralDBElement; @@ -33,83 +36,77 @@ use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; /** - * Class Store location. - * - * @ORM\Entity(repositoryClass="App\Repository\Parts\StorelocationRepository") - * @ORM\Table("`storelocations`", indexes={ - * @ORM\Index(name="location_idx_name", columns={"name"}), - * @ORM\Index(name="location_idx_parent_name", columns={"parent_id", "name"}), - * }) + * This entity represents a storage location, where parts can be stored. + * @extends AbstractPartsContainingDBElement */ +#[ORM\Entity(repositoryClass: StorelocationRepository::class)] +#[ORM\Table('`storelocations`')] +#[ORM\Index(name: 'location_idx_name', columns: ['name'])] +#[ORM\Index(name: 'location_idx_parent_name', columns: ['parent_id', 'name'])] class Storelocation extends AbstractPartsContainingDBElement { - /** - * @ORM\OneToMany(targetEntity="Storelocation", mappedBy="parent") - * @ORM\OrderBy({"name" = "ASC"}) - * @var Collection - */ + #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $children; - /** - * @ORM\ManyToOne(targetEntity="Storelocation", inversedBy="children") - * @ORM\JoinColumn(name="parent_id", referencedColumnName="id") - */ - protected ?AbstractStructuralDBElement $parent; + #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'parent_id')] + protected ?AbstractStructuralDBElement $parent = null; /** * @var MeasurementUnit|null The measurement unit, which parts can be stored in here - * @ORM\ManyToOne(targetEntity="MeasurementUnit") - * @ORM\JoinColumn(name="storage_type_id", referencedColumnName="id") */ + #[ORM\ManyToOne(targetEntity: MeasurementUnit::class)] + #[ORM\JoinColumn(name: 'storage_type_id')] protected ?MeasurementUnit $storage_type = null; /** @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Parameters\StorelocationParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: StorelocationParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; /** * @var bool - * @ORM\Column(type="boolean") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $is_full = false; /** * @var bool - * @ORM\Column(type="boolean") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $only_single_part = false; /** * @var bool - * @ORM\Column(type="boolean") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $limit_to_existing_parts = false; /** * @var User|null The owner of this storage location - * @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User") - * @ORM\JoinColumn(name="id_owner", referencedColumnName="id", nullable=true, onDelete="SET NULL") - * @Assert\Expression("this.getOwner() == null or this.getOwner().isAnonymousUser() === false", message="validator.part_lot.owner_must_not_be_anonymous") */ + #[Assert\Expression('this.getOwner() == null or this.getOwner().isAnonymousUser() === false', message: 'validator.part_lot.owner_must_not_be_anonymous')] + #[ORM\ManyToOne(targetEntity: User::class)] + #[ORM\JoinColumn(name: 'id_owner', onDelete: 'SET NULL')] protected ?User $owner = null; /** * @var bool If this is set to true, only parts lots, which are owned by the same user as the store location are allowed to be stored here. - * @ORM\Column(type="boolean", options={"default":false}) */ + #[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])] protected bool $part_owner_must_match = false; /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\StorelocationAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: StorelocationAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] protected Collection $attachments; /******************************************************************************** @@ -139,9 +136,6 @@ class Storelocation extends AbstractPartsContainingDBElement return $this->only_single_part; } - /** - * @return Storelocation - */ public function setOnlySinglePart(bool $only_single_part): self { $this->only_single_part = $only_single_part; @@ -157,9 +151,6 @@ class Storelocation extends AbstractPartsContainingDBElement return $this->limit_to_existing_parts; } - /** - * @return Storelocation - */ public function setLimitToExistingParts(bool $limit_to_existing_parts): self { $this->limit_to_existing_parts = $limit_to_existing_parts; @@ -172,9 +163,6 @@ class Storelocation extends AbstractPartsContainingDBElement return $this->storage_type; } - /** - * @return Storelocation - */ public function setStorageType(?MeasurementUnit $storage_type): self { $this->storage_type = $storage_type; @@ -184,7 +172,6 @@ class Storelocation extends AbstractPartsContainingDBElement /** * Returns the owner of this storage location - * @return User|null */ public function getOwner(): ?User { @@ -193,8 +180,6 @@ class Storelocation extends AbstractPartsContainingDBElement /** * Sets the owner of this storage location - * @param User|null $owner - * @return Storelocation */ public function setOwner(?User $owner): Storelocation { @@ -204,7 +189,6 @@ class Storelocation extends AbstractPartsContainingDBElement /** * If this is set to true, only parts lots, which are owned by the same user as the store location are allowed to be stored here. - * @return bool */ public function isPartOwnerMustMatch(): bool { @@ -213,8 +197,6 @@ class Storelocation extends AbstractPartsContainingDBElement /** * If this is set to true, only parts lots, which are owned by the same user as the store location are allowed to be stored here. - * @param bool $part_owner_must_match - * @return Storelocation */ public function setPartOwnerMustMatch(bool $part_owner_must_match): Storelocation { @@ -230,7 +212,6 @@ class Storelocation extends AbstractPartsContainingDBElement * Setters * *********************************************************************************/ - /** * Change the "is full" attribute of this store location. * @@ -239,8 +220,6 @@ class Storelocation extends AbstractPartsContainingDBElement * * @param bool $new_is_full * true means that the storelocation is full * * false means that the storelocation isn't full - * - * @return Storelocation */ public function setIsFull(bool $new_is_full): self { @@ -248,4 +227,11 @@ class Storelocation extends AbstractPartsContainingDBElement return $this; } + public function __construct() + { + parent::__construct(); + $this->children = new ArrayCollection(); + $this->parameters = new ArrayCollection(); + $this->attachments = new ArrayCollection(); + } } diff --git a/src/Entity/Parts/Supplier.php b/src/Entity/Parts/Supplier.php index f8ab4202..34483ca3 100644 --- a/src/Entity/Parts/Supplier.php +++ b/src/Entity/Parts/Supplier.php @@ -22,6 +22,9 @@ declare(strict_types=1); namespace App\Entity\Parts; +use App\Repository\Parts\SupplierRepository; +use App\Entity\PriceInformations\Orderdetail; +use Doctrine\Common\Collections\ArrayCollection; use App\Entity\Attachments\SupplierAttachment; use App\Entity\Base\AbstractCompany; use App\Entity\Base\AbstractStructuralDBElement; @@ -36,64 +39,63 @@ use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; /** - * Class Supplier. + * This entity represents a supplier of parts (the company that sells the parts). * - * @ORM\Entity(repositoryClass="App\Repository\Parts\SupplierRepository") - * @ORM\Table("`suppliers`", indexes={ - * @ORM\Index(name="supplier_idx_name", columns={"name"}), - * @ORM\Index(name="supplier_idx_parent_name", columns={"parent_id", "name"}), - * }) + * @extends AbstractCompany */ +#[ORM\Entity(repositoryClass: SupplierRepository::class)] +#[ORM\Table('`suppliers`')] +#[ORM\Index(name: 'supplier_idx_name', columns: ['name'])] +#[ORM\Index(name: 'supplier_idx_parent_name', columns: ['parent_id', 'name'])] class Supplier extends AbstractCompany { - /** - * @ORM\OneToMany(targetEntity="Supplier", mappedBy="parent") - * @ORM\OrderBy({"name" = "ASC"}) - * @var Collection - */ + #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $children; - /** - * @ORM\ManyToOne(targetEntity="Supplier", inversedBy="children") - * @ORM\JoinColumn(name="parent_id", referencedColumnName="id") - */ - protected ?AbstractStructuralDBElement $parent; + #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'parent_id')] + protected ?AbstractStructuralDBElement $parent = null; /** - * @ORM\OneToMany(targetEntity="App\Entity\PriceInformations\Orderdetail", mappedBy="supplier") + * @var Collection|Orderdetail[] */ + /** + * @var Collection|Orderdetail[] + */ + #[ORM\OneToMany(targetEntity: Orderdetail::class, mappedBy: 'supplier')] protected Collection $orderdetails; /** * @var Currency|null The currency that should be used by default for order informations with this supplier. * Set to null, to use global base currency. - * @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency") - * @ORM\JoinColumn(name="default_currency_id", referencedColumnName="id", nullable=true) - * @Selectable() */ + #[ORM\ManyToOne(targetEntity: Currency::class)] + #[ORM\JoinColumn(name: 'default_currency_id')] + #[Selectable()] protected ?Currency $default_currency = null; /** * @var BigDecimal|null the shipping costs that have to be paid, when ordering via this supplier - * @ORM\Column(name="shipping_costs", nullable=true, type="big_decimal", precision=11, scale=5) - * @Groups({"extended", "full", "import"}) - * @BigDecimalPositiveOrZero() */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(name: 'shipping_costs', nullable: true, type: 'big_decimal', precision: 11, scale: 5)] + #[BigDecimalPositiveOrZero()] protected ?BigDecimal $shipping_costs = null; /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\SupplierAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: SupplierAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; /** @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Parameters\SupplierParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: SupplierParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; /** @@ -106,8 +108,6 @@ class Supplier extends AbstractCompany /** * Sets the default currency. - * - * @return Supplier */ public function setDefaultCurrency(?Currency $default_currency): self { @@ -130,12 +130,10 @@ class Supplier extends AbstractCompany * Sets the shipping costs for an order with this supplier. * * @param BigDecimal|null $shipping_costs a BigDecimal with the shipping costs - * - * @return Supplier */ public function setShippingCosts(?BigDecimal $shipping_costs): self { - if (null === $shipping_costs) { + if (!$shipping_costs instanceof BigDecimal) { $this->shipping_costs = null; } @@ -146,4 +144,12 @@ class Supplier extends AbstractCompany return $this; } + public function __construct() + { + parent::__construct(); + $this->children = new ArrayCollection(); + $this->orderdetails = new ArrayCollection(); + $this->attachments = new ArrayCollection(); + $this->parameters = new ArrayCollection(); + } } diff --git a/src/Entity/PriceInformations/Currency.php b/src/Entity/PriceInformations/Currency.php index 6b78de19..7e51c4c4 100644 --- a/src/Entity/PriceInformations/Currency.php +++ b/src/Entity/PriceInformations/Currency.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use Doctrine\DBAL\Types\Types; use App\Entity\Attachments\CurrencyAttachment; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Parameters\CurrencyParameter; @@ -38,67 +39,66 @@ use Symfony\Component\Validator\Constraints as Assert; /** * This entity describes a currency that can be used for price information. * - * @UniqueEntity("iso_code") - * @ORM\Entity() - * @ORM\Table(name="currencies", indexes={ - * @ORM\Index(name="currency_idx_name", columns={"name"}), - * @ORM\Index(name="currency_idx_parent_name", columns={"parent_id", "name"}), - * }) + * @extends AbstractStructuralDBElement */ +#[UniqueEntity('iso_code')] +#[ORM\Entity] +#[ORM\Table(name: 'currencies')] +#[ORM\Index(name: 'currency_idx_name', columns: ['name'])] +#[ORM\Index(name: 'currency_idx_parent_name', columns: ['parent_id', 'name'])] class Currency extends AbstractStructuralDBElement { - public const PRICE_SCALE = 5; + final public const PRICE_SCALE = 5; /** * @var BigDecimal|null The exchange rate between this currency and the base currency * (how many base units the current currency is worth) - * @ORM\Column(type="big_decimal", precision=11, scale=5, nullable=true) - * @BigDecimalPositive() */ + #[ORM\Column(type: 'big_decimal', precision: 11, scale: 5, nullable: true)] + #[BigDecimalPositive()] protected ?BigDecimal $exchange_rate = null; /** * @var string the 3-letter ISO code of the currency - * @ORM\Column(type="string") - * @Assert\Currency() - * @Groups({"extended", "full", "import"}) */ + #[Assert\Currency] + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::STRING)] protected string $iso_code = ""; - /** - * @ORM\OneToMany(targetEntity="Currency", mappedBy="parent", cascade={"persist"}) - * @ORM\OrderBy({"name" = "ASC"}) - */ + #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent', cascade: ['persist'])] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $children; - /** - * @ORM\ManyToOne(targetEntity="Currency", inversedBy="children") - * @ORM\JoinColumn(name="parent_id", referencedColumnName="id") - */ - protected ?AbstractStructuralDBElement $parent; + #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'parent_id')] + protected ?AbstractStructuralDBElement $parent = null; /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\CurrencyAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: CurrencyAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; /** @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Parameters\CurrencyParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: CurrencyParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; /** @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\PriceInformations\Pricedetail", mappedBy="currency") */ + #[ORM\OneToMany(targetEntity: Pricedetail::class, mappedBy: 'currency')] protected Collection $pricedetails; public function __construct() { + $this->children = new ArrayCollection(); + $this->attachments = new ArrayCollection(); + $this->parameters = new ArrayCollection(); $this->pricedetails = new ArrayCollection(); parent::__construct(); } @@ -118,11 +118,6 @@ class Currency extends AbstractStructuralDBElement return $this->iso_code; } - /** - * @param string|null $iso_code - * - * @return Currency - */ public function setIsoCode(?string $iso_code): self { $this->iso_code = $iso_code; @@ -137,7 +132,7 @@ class Currency extends AbstractStructuralDBElement { $tmp = $this->getExchangeRate(); - if (null === $tmp || $tmp->isZero()) { + if (!$tmp instanceof BigDecimal || $tmp->isZero()) { return null; } @@ -158,12 +153,10 @@ class Currency extends AbstractStructuralDBElement * * @param BigDecimal|null $exchange_rate The new exchange rate of the currency. * Set to null, if the exchange rate is unknown. - * - * @return Currency */ public function setExchangeRate(?BigDecimal $exchange_rate): self { - if (null === $exchange_rate) { + if (!$exchange_rate instanceof BigDecimal) { $this->exchange_rate = null; } $tmp = $exchange_rate->toScale(self::PRICE_SCALE, RoundingMode::HALF_UP); diff --git a/src/Entity/PriceInformations/Orderdetail.php b/src/Entity/PriceInformations/Orderdetail.php index c1d182a2..ec5e9ce5 100644 --- a/src/Entity/PriceInformations/Orderdetail.php +++ b/src/Entity/PriceInformations/Orderdetail.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\TimestampTrait; use App\Entity\Contracts\NamedElementInterface; @@ -39,63 +40,59 @@ use Symfony\Component\Validator\Constraints as Assert; /** * Class Orderdetail. - * - * @ORM\Table("`orderdetails`", indexes={ - * @ORM\Index(name="orderdetails_supplier_part_nr", columns={"supplierpartnr"}), - * }) - * @ORM\Entity() - * @ORM\HasLifecycleCallbacks() - * @UniqueEntity({"supplierpartnr", "supplier", "part"}) */ +#[UniqueEntity(['supplierpartnr', 'supplier', 'part'])] +#[ORM\Entity] +#[ORM\HasLifecycleCallbacks] +#[ORM\Table('`orderdetails`')] +#[ORM\Index(name: 'orderdetails_supplier_part_nr', columns: ['supplierpartnr'])] class Orderdetail extends AbstractDBElement implements TimeStampableInterface, NamedElementInterface { use TimestampTrait; - /** - * @ORM\OneToMany(targetEntity="Pricedetail", mappedBy="orderdetail", cascade={"persist", "remove"}, orphanRemoval=true) - * @Assert\Valid() - * @ORM\OrderBy({"min_discount_quantity" = "ASC"}) - * @Groups({"extended", "full", "import"}) - */ + #[Assert\Valid] + #[Groups(['extended', 'full', 'import'])] + #[ORM\OneToMany(targetEntity: Pricedetail::class, mappedBy: 'orderdetail', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['min_discount_quantity' => 'ASC'])] protected Collection $pricedetails; /** * @var string - * @ORM\Column(type="string") - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::STRING)] protected string $supplierpartnr = ''; /** * @var bool - * @ORM\Column(type="boolean") - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $obsolete = false; /** * @var string - * @ORM\Column(type="string") - * @Assert\Url() - * @Groups({"full", "import"}) */ + #[Assert\Url] + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::STRING)] protected string $supplier_product_url = ''; /** * @var Part|null - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Part", inversedBy="orderdetails") - * @ORM\JoinColumn(name="part_id", referencedColumnName="id", nullable=false, onDelete="CASCADE") - * @Assert\NotNull() */ + #[Assert\NotNull] + #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'orderdetails')] + #[ORM\JoinColumn(name: 'part_id', nullable: false, onDelete: 'CASCADE')] protected ?Part $part = null; /** * @var Supplier|null - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Supplier", inversedBy="orderdetails") - * @ORM\JoinColumn(name="id_supplier", referencedColumnName="id") - * @Assert\NotNull(message="validator.orderdetail.supplier_must_not_be_null") - * @Groups({"extended", "full", "import"}) */ + #[Assert\NotNull(message: 'validator.orderdetail.supplier_must_not_be_null')] + #[Groups(['extended', 'full', 'import'])] + #[ORM\ManyToOne(targetEntity: Supplier::class, inversedBy: 'orderdetails')] + #[ORM\JoinColumn(name: 'id_supplier')] protected ?Supplier $supplier = null; public function __construct() @@ -119,14 +116,13 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N /** * Helper for updating the timestamp. It is automatically called by doctrine before persisting. - * - * @ORM\PrePersist - * @ORM\PreUpdate */ + #[ORM\PrePersist] + #[ORM\PreUpdate] public function updateTimestamps(): void { $this->lastModified = new DateTime('now'); - if (null === $this->addedDate) { + if (!$this->addedDate instanceof \DateTimeInterface) { $this->addedDate = new DateTime('now'); } @@ -199,7 +195,7 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N return $this->supplier_product_url; } - if (null === $this->getSupplier()) { + if (!$this->getSupplier() instanceof Supplier) { return ''; } @@ -209,8 +205,7 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N /** * Get all pricedetails. * - * @return Pricedetail[]|Collection all pricedetails as a one-dimensional array of Pricedetails objects, - * sorted by minimum discount quantity + * @return Collection */ public function getPricedetails(): Collection { @@ -221,8 +216,6 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N * Adds a price detail to this orderdetail. * * @param Pricedetail $pricedetail The pricedetail to add - * - * @return Orderdetail */ public function addPricedetail(Pricedetail $pricedetail): self { @@ -234,8 +227,6 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N /** * Removes a price detail from this orderdetail. - * - * @return Orderdetail */ public function removePricedetail(Pricedetail $pricedetail): self { @@ -278,11 +269,8 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N * Setters * *********************************************************************************/ - /** * Sets a new part with which this orderdetail is associated. - * - * @return Orderdetail */ public function setPart(Part $part): self { @@ -293,8 +281,6 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N /** * Sets the new supplier associated with this orderdetail. - * - * @return Orderdetail */ public function setSupplier(Supplier $new_supplier): self { @@ -307,9 +293,6 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N * Set the supplier part-nr. * * @param string $new_supplierpartnr the new supplier-part-nr - * - * @return Orderdetail - * @return Orderdetail */ public function setSupplierpartnr(string $new_supplierpartnr): self { @@ -322,9 +305,6 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N * Set if the part is obsolete at the supplier of that orderdetails. * * @param bool $new_obsolete true means that this part is obsolete - * - * @return Orderdetail - * @return Orderdetail */ public function setObsolete(bool $new_obsolete): self { @@ -338,8 +318,6 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N * Set this to "", if the function getSupplierProductURL should return the automatic generated URL. * * @param string $new_url The new URL for the supplier URL - * - * @return Orderdetail */ public function setSupplierProductUrl(string $new_url): self { diff --git a/src/Entity/PriceInformations/Pricedetail.php b/src/Entity/PriceInformations/Pricedetail.php index 49d01730..26afbb50 100644 --- a/src/Entity/PriceInformations/Pricedetail.php +++ b/src/Entity/PriceInformations/Pricedetail.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\PriceInformations; +use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\TimestampTrait; use App\Entity\Contracts\TimeStampableInterface; @@ -37,67 +38,65 @@ use Symfony\Component\Validator\Constraints as Assert; /** * Class Pricedetail. - * - * @ORM\Entity() - * @ORM\Table("`pricedetails`", indexes={ - * @ORM\Index(name="pricedetails_idx_min_discount", columns={"min_discount_quantity"}), - * @ORM\Index(name="pricedetails_idx_min_discount_price_qty", columns={"min_discount_quantity", "price_related_quantity"}), - * }) - * @ORM\HasLifecycleCallbacks() - * @UniqueEntity(fields={"min_discount_quantity", "orderdetail"}) */ +#[UniqueEntity(fields: ['min_discount_quantity', 'orderdetail'])] +#[ORM\Entity] +#[ORM\HasLifecycleCallbacks] +#[ORM\Table('`pricedetails`')] +#[ORM\Index(name: 'pricedetails_idx_min_discount', columns: ['min_discount_quantity'])] +#[ORM\Index(name: 'pricedetails_idx_min_discount_price_qty', columns: ['min_discount_quantity', 'price_related_quantity'])] class Pricedetail extends AbstractDBElement implements TimeStampableInterface { use TimestampTrait; - public const PRICE_PRECISION = 5; + final public const PRICE_PRECISION = 5; /** * @var BigDecimal The price related to the detail. (Given in the selected currency) - * @ORM\Column(type="big_decimal", precision=11, scale=5) - * @BigDecimalPositive() - * @Groups({"extended", "full"}) */ + #[Groups(['extended', 'full'])] + #[ORM\Column(type: 'big_decimal', precision: 11, scale: 5)] + #[BigDecimalPositive()] protected BigDecimal $price; /** * @var ?Currency The currency used for the current price information. - * If this is null, the global base unit is assumed. - * @ORM\ManyToOne(targetEntity="Currency", inversedBy="pricedetails") - * @ORM\JoinColumn(name="id_currency", referencedColumnName="id", nullable=true) - * @Selectable() - * @Groups({"extended", "full", "import"}) + * If this is null, the global base unit is assumed */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\ManyToOne(targetEntity: Currency::class, inversedBy: 'pricedetails')] + #[ORM\JoinColumn(name: 'id_currency')] + #[Selectable()] protected ?Currency $currency = null; /** * @var float - * @ORM\Column(type="float") - * @Assert\Positive() - * @Groups({"extended", "full", "import"}) */ + #[Assert\Positive] + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::FLOAT)] protected float $price_related_quantity = 1.0; /** * @var float - * @ORM\Column(type="float") - * @Assert\Positive() - * @Groups({"extended", "full", "import"}) */ + #[Assert\Positive] + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::FLOAT)] protected float $min_discount_quantity = 1.0; /** * @var bool - * @ORM\Column(type="boolean") */ + #[ORM\Column(type: Types::BOOLEAN)] protected bool $manual_input = true; /** * @var Orderdetail|null - * @ORM\ManyToOne(targetEntity="Orderdetail", inversedBy="pricedetails") - * @ORM\JoinColumn(name="orderdetails_id", referencedColumnName="id", nullable=false, onDelete="CASCADE") - * @Assert\NotNull() */ + #[Assert\NotNull] + #[ORM\ManyToOne(targetEntity: Orderdetail::class, inversedBy: 'pricedetails')] + #[ORM\JoinColumn(name: 'orderdetails_id', nullable: false, onDelete: 'CASCADE')] protected ?Orderdetail $orderdetail = null; public function __construct() @@ -115,14 +114,13 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface /** * Helper for updating the timestamp. It is automatically called by doctrine before persisting. - * - * @ORM\PrePersist - * @ORM\PreUpdate */ + #[ORM\PrePersist] + #[ORM\PreUpdate] public function updateTimestamps(): void { $this->lastModified = new DateTime('now'); - if (null === $this->addedDate) { + if (!$this->addedDate instanceof \DateTimeInterface) { $this->addedDate = new DateTime('now'); } @@ -169,7 +167,7 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface * * @return BigDecimal the price as a bcmath string */ - public function getPricePerUnit($multiplier = 1.0): BigDecimal + public function getPricePerUnit(float|string|BigDecimal $multiplier = 1.0): BigDecimal { $tmp = BigDecimal::of($multiplier); $tmp = $tmp->multipliedBy($this->price); @@ -251,8 +249,6 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface /** * Sets the currency associated with the price information. * Set to null, to use the global base currency. - * - * @return Pricedetail */ public function setCurrency(?Currency $currency): self { diff --git a/src/Entity/ProjectSystem/Project.php b/src/Entity/ProjectSystem/Project.php index ce55b50a..352d42c4 100644 --- a/src/Entity/ProjectSystem/Project.php +++ b/src/Entity/ProjectSystem/Project.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\ProjectSystem; +use App\Repository\Parts\DeviceRepository; +use Doctrine\DBAL\Types\Types; use App\Entity\Attachments\ProjectAttachment; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Parameters\ProjectParameter; @@ -35,75 +37,63 @@ use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Context\ExecutionContextInterface; /** - * Class AttachmentType. + * This class represents a project in the database. * - * @ORM\Entity(repositoryClass="App\Repository\Parts\DeviceRepository") - * @ORM\Table(name="projects") + * @extends AbstractStructuralDBElement */ +#[ORM\Entity(repositoryClass: DeviceRepository::class)] +#[ORM\Table(name: 'projects')] class Project extends AbstractStructuralDBElement { - /** - * @ORM\OneToMany(targetEntity="Project", mappedBy="parent") - * @ORM\OrderBy({"name" = "ASC"}) - * @var Collection - */ + #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $children; - /** - * @ORM\ManyToOne(targetEntity="Project", inversedBy="children") - * @ORM\JoinColumn(name="parent_id", referencedColumnName="id") - */ - protected ?AbstractStructuralDBElement $parent; + #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'parent_id')] + protected ?AbstractStructuralDBElement $parent = null; - /** - * @ORM\OneToMany(targetEntity="ProjectBOMEntry", mappedBy="project", cascade={"persist", "remove"}, orphanRemoval=true) - * @Assert\Valid() - * @Groups({"extended", "full"}) - */ + #[Assert\Valid] + #[Groups(['extended', 'full'])] + #[ORM\OneToMany(targetEntity: ProjectBOMEntry::class, mappedBy: 'project', cascade: ['persist', 'remove'], orphanRemoval: true)] protected Collection $bom_entries; - /** - * @ORM\Column(type="integer") - */ + #[ORM\Column(type: Types::INTEGER)] protected int $order_quantity = 0; /** * @var string|null The current status of the project - * @ORM\Column(type="string", length=64, nullable=true) - * @Assert\Choice({"draft","planning","in_production","finished","archived"}) - * @Groups({"extended", "full"}) */ + #[Assert\Choice(['draft', 'planning', 'in_production', 'finished', 'archived'])] + #[Groups(['extended', 'full'])] + #[ORM\Column(type: Types::STRING, length: 64, nullable: true)] protected ?string $status = null; /** * @var Part|null The (optional) part that represents the builds of this project in the stock - * @ORM\OneToOne(targetEntity="App\Entity\Parts\Part", mappedBy="built_project", cascade={"persist"}, orphanRemoval=true) */ + #[ORM\OneToOne(targetEntity: Part::class, mappedBy: 'built_project', cascade: ['persist'], orphanRemoval: true)] protected ?Part $build_part = null; - /** - * @ORM\Column(type="boolean") - */ + #[ORM\Column(type: Types::BOOLEAN)] protected bool $order_only_missing_parts = false; - /** - * @ORM\Column(type="text", nullable=false) - * @Groups({"simple", "extended", "full"}) - */ + #[Groups(['simple', 'extended', 'full'])] + #[ORM\Column(type: Types::TEXT)] protected string $description = ''; /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\ProjectAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"name" = "ASC"}) */ + #[ORM\OneToMany(targetEntity: ProjectAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; /** @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Parameters\ProjectParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) */ + #[ORM\OneToMany(targetEntity: ProjectParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; /******************************************************************************** @@ -114,6 +104,8 @@ class Project extends AbstractStructuralDBElement public function __construct() { + $this->attachments = new ArrayCollection(); + $this->parameters = new ArrayCollection(); parent::__construct(); $this->bom_entries = new ArrayCollection(); $this->children = new ArrayCollection(); @@ -183,8 +175,6 @@ class Project extends AbstractStructuralDBElement * Set the "order_only_missing_parts" attribute. * * @param bool $new_order_only_missing_parts the new "order_only_missing_parts" attribute - * - * @return Project */ public function setOrderOnlyMissingParts(bool $new_order_only_missing_parts): self { @@ -193,16 +183,12 @@ class Project extends AbstractStructuralDBElement return $this; } - /** - * @return Collection|ProjectBOMEntry[] - */ public function getBomEntries(): Collection { return $this->bom_entries; } /** - * @param ProjectBOMEntry $entry * @return $this */ public function addBomEntry(ProjectBOMEntry $entry): self @@ -213,7 +199,6 @@ class Project extends AbstractStructuralDBElement } /** - * @param ProjectBOMEntry $entry * @return $this */ public function removeBomEntry(ProjectBOMEntry $entry): self @@ -222,18 +207,11 @@ class Project extends AbstractStructuralDBElement return $this; } - /** - * @return string - */ public function getDescription(): string { return $this->description; } - /** - * @param string $description - * @return Project - */ public function setDescription(string $description): Project { $this->description = $description; @@ -258,16 +236,14 @@ class Project extends AbstractStructuralDBElement /** * Checks if this project has an associated part representing the builds of this project in the stock. - * @return bool */ public function hasBuildPart(): bool { - return $this->build_part !== null; + return $this->build_part instanceof Part; } /** * Gets the part representing the builds of this project in the stock, if it is existing - * @return Part|null */ public function getBuildPart(): ?Part { @@ -276,25 +252,22 @@ class Project extends AbstractStructuralDBElement /** * Sets the part representing the builds of this project in the stock. - * @param Part|null $build_part */ public function setBuildPart(?Part $build_part): void { $this->build_part = $build_part; - if ($build_part) { + if ($build_part instanceof Part) { $build_part->setBuiltProject($this); } } - /** - * @Assert\Callback - */ - public function validate(ExecutionContextInterface $context, $payload) + #[Assert\Callback] + public function validate(ExecutionContextInterface $context, $payload): void { //If this project has subprojects, and these have builds part, they must be included in the BOM foreach ($this->getChildren() as $child) { /** @var $child Project */ - if ($child->getBuildPart() === null) { + if (!$child->getBuildPart() instanceof Part) { continue; } //We have to search all bom entries for the build part diff --git a/src/Entity/ProjectSystem/ProjectBOMEntry.php b/src/Entity/ProjectSystem/ProjectBOMEntry.php index a14f5f2d..af974b5f 100644 --- a/src/Entity/ProjectSystem/ProjectBOMEntry.php +++ b/src/Entity/ProjectSystem/ProjectBOMEntry.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Entity\ProjectSystem; +use Doctrine\DBAL\Types\Types; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\TimestampTrait; use App\Entity\Parts\Part; @@ -36,114 +37,88 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface; /** * The ProjectBOMEntry class represents an entry in a project's BOM. - * - * @ORM\Table("project_bom_entries") - * @ORM\HasLifecycleCallbacks() - * @ORM\Entity() - * @UniqueEntity(fields={"part", "project"}, message="project.bom_entry.part_already_in_bom") - * @UniqueEntity(fields={"name", "project"}, message="project.bom_entry.name_already_in_bom", ignoreNull=true) */ +#[UniqueEntity(fields: ['part', 'project'], message: 'project.bom_entry.part_already_in_bom')] +#[UniqueEntity(fields: ['name', 'project'], message: 'project.bom_entry.name_already_in_bom', ignoreNull: true)] +#[ORM\HasLifecycleCallbacks] +#[ORM\Entity] +#[ORM\Table('project_bom_entries')] class ProjectBOMEntry extends AbstractDBElement { use TimestampTrait; - /** - * @var float - * @ORM\Column(type="float", name="quantity") - * @Assert\Positive() - */ - protected float $quantity; + #[Assert\Positive] + #[ORM\Column(type: Types::FLOAT, name: 'quantity')] + protected float $quantity = 1.0; /** * @var string A comma separated list of the names, where this parts should be placed - * @ORM\Column(type="text", name="mountnames") */ + #[ORM\Column(type: Types::TEXT, name: 'mountnames')] protected string $mountnames = ''; /** * @var string|null An optional name describing this BOM entry (useful for non-part entries) - * @ORM\Column(type="string", nullable=true) - * @Assert\Expression( - * "this.getPart() !== null or this.getName() !== null", - * message="validator.project.bom_entry.name_or_part_needed" - * ) */ + #[Assert\Expression('this.getPart() !== null or this.getName() !== null', message: 'validator.project.bom_entry.name_or_part_needed')] + #[ORM\Column(type: Types::STRING, nullable: true)] protected ?string $name = null; /** * @var string An optional comment for this BOM entry - * @ORM\Column(type="text") */ - protected string $comment; + #[ORM\Column(type: Types::TEXT)] + protected string $comment = ''; /** * @var Project|null - * @ORM\ManyToOne(targetEntity="Project", inversedBy="bom_entries") - * @ORM\JoinColumn(name="id_device", referencedColumnName="id") */ + #[ORM\ManyToOne(targetEntity: Project::class, inversedBy: 'bom_entries')] + #[ORM\JoinColumn(name: 'id_device')] protected ?Project $project = null; /** * @var Part|null The part associated with this - * @ORM\ManyToOne(targetEntity="App\Entity\Parts\Part", inversedBy="project_bom_entries") - * @ORM\JoinColumn(name="id_part", referencedColumnName="id", nullable=true) */ + #[ORM\ManyToOne(targetEntity: Part::class, inversedBy: 'project_bom_entries')] + #[ORM\JoinColumn(name: 'id_part')] protected ?Part $part = null; /** * @var BigDecimal|null The price of this non-part BOM entry - * @ORM\Column(type="big_decimal", precision=11, scale=5, nullable=true) - * @Assert\AtLeastOneOf({ - * @BigDecimalPositive(), - * @Assert\IsNull() - * }) */ - protected ?BigDecimal $price; + #[Assert\AtLeastOneOf([new BigDecimalPositive(), new Assert\IsNull()])] + #[ORM\Column(type: 'big_decimal', precision: 11, scale: 5, nullable: true)] + protected ?BigDecimal $price = null; /** * @var ?Currency The currency for the price of this non-part BOM entry - * @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency") - * @ORM\JoinColumn(nullable=true) - * @Selectable() */ + #[ORM\ManyToOne(targetEntity: Currency::class)] + #[ORM\JoinColumn] + #[Selectable] protected ?Currency $price_currency = null; public function __construct() { - //$this->price = BigDecimal::zero()->toScale(5); - $this->price = null; } - /** - * @return float - */ public function getQuantity(): float { return $this->quantity; } - /** - * @param float $quantity - * @return ProjectBOMEntry - */ public function setQuantity(float $quantity): ProjectBOMEntry { $this->quantity = $quantity; return $this; } - /** - * @return string - */ public function getMountnames(): string { return $this->mountnames; } - /** - * @param string $mountnames - * @return ProjectBOMEntry - */ public function setMountnames(string $mountnames): ProjectBOMEntry { $this->mountnames = $mountnames; @@ -160,7 +135,6 @@ class ProjectBOMEntry extends AbstractDBElement /** * @param string $name - * @return ProjectBOMEntry */ public function setName(?string $name): ProjectBOMEntry { @@ -168,36 +142,22 @@ class ProjectBOMEntry extends AbstractDBElement return $this; } - /** - * @return string - */ public function getComment(): string { return $this->comment; } - /** - * @param string $comment - * @return ProjectBOMEntry - */ public function setComment(string $comment): ProjectBOMEntry { $this->comment = $comment; return $this; } - /** - * @return Project|null - */ public function getProject(): ?Project { return $this->project; } - /** - * @param Project|null $project - * @return ProjectBOMEntry - */ public function setProject(?Project $project): ProjectBOMEntry { $this->project = $project; @@ -206,18 +166,11 @@ class ProjectBOMEntry extends AbstractDBElement - /** - * @return Part|null - */ public function getPart(): ?Part { return $this->part; } - /** - * @param Part|null $part - * @return ProjectBOMEntry - */ public function setPart(?Part $part): ProjectBOMEntry { $this->part = $part; @@ -227,7 +180,6 @@ class ProjectBOMEntry extends AbstractDBElement /** * Returns the price of this BOM entry, if existing. * Prices are only valid on non-Part BOM entries. - * @return BigDecimal|null */ public function getPrice(): ?BigDecimal { @@ -237,24 +189,17 @@ class ProjectBOMEntry extends AbstractDBElement /** * Sets the price of this BOM entry. * Prices are only valid on non-Part BOM entries. - * @param BigDecimal|null $price */ public function setPrice(?BigDecimal $price): void { $this->price = $price; } - /** - * @return Currency|null - */ public function getPriceCurrency(): ?Currency { return $this->price_currency; } - /** - * @param Currency|null $price_currency - */ public function setPriceCurrency(?Currency $price_currency): void { $this->price_currency = $price_currency; @@ -266,22 +211,18 @@ class ProjectBOMEntry extends AbstractDBElement */ public function isPartBomEntry(): bool { - return $this->part !== null; + return $this->part instanceof Part; } - /** - * @Assert\Callback - */ + #[Assert\Callback] public function validate(ExecutionContextInterface $context, $payload): void { //Round quantity to whole numbers, if the part is not a decimal part - if ($this->part) { - if (!$this->part->getPartUnit() || $this->part->getPartUnit()->isInteger()) { - $this->quantity = round($this->quantity); - } + if ($this->part instanceof Part && (!$this->part->getPartUnit() || $this->part->getPartUnit()->isInteger())) { + $this->quantity = round($this->quantity); } //Non-Part BOM entries are rounded - if ($this->part === null) { + if (!$this->part instanceof Part) { $this->quantity = round($this->quantity); } @@ -298,21 +239,21 @@ class ProjectBOMEntry extends AbstractDBElement } //Check that the number of mountnames is the same as the (rounded) quantity - if (!empty($this->mountnames) && count($uniq_mountnames) !== (int) round ($this->quantity)) { + if ($this->mountnames !== '' && count($uniq_mountnames) !== (int) round ($this->quantity)) { $context->buildViolation('project.bom_entry.mountnames_quantity_mismatch') ->atPath('mountnames') ->addViolation(); } //Prices are only allowed on non-part BOM entries - if ($this->part !== null && $this->price !== null) { + if ($this->part instanceof Part && $this->price instanceof BigDecimal) { $context->buildViolation('project.bom_entry.price_not_allowed_on_parts') ->atPath('price') ->addViolation(); } //Check that the part is not the build representation part of this device or one of its parents - if ($this->part && $this->part->getBuiltProject() !== null) { + if ($this->part && $this->part->getBuiltProject() instanceof Project) { //Get the associated project $associated_project = $this->part->getBuiltProject(); //Check that it is not the same as the current project neither one of its parents diff --git a/src/Entity/UserSystem/Group.php b/src/Entity/UserSystem/Group.php index 74a600fe..b56cabf4 100644 --- a/src/Entity/UserSystem/Group.php +++ b/src/Entity/UserSystem/Group.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use App\Validator\Constraints\NoLockout; +use Doctrine\DBAL\Types\Types; use App\Entity\Attachments\GroupAttachment; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Parameters\GroupParameter; @@ -36,64 +38,61 @@ use Symfony\Component\Validator\Constraints as Assert; /** * This entity represents a user group. * - * @ORM\Entity() - * @ORM\Table("`groups`", indexes={ - * @ORM\Index(name="group_idx_name", columns={"name"}), - * @ORM\Index(name="group_idx_parent_name", columns={"parent_id", "name"}), - * }) + * @extends AbstractStructuralDBElement */ +#[ORM\Entity] +#[ORM\Table('`groups`')] +#[ORM\Index(name: 'group_idx_name', columns: ['name'])] +#[ORM\Index(name: 'group_idx_parent_name', columns: ['parent_id', 'name'])] +#[NoLockout()] class Group extends AbstractStructuralDBElement implements HasPermissionsInterface { - /** - * @ORM\OneToMany(targetEntity="Group", mappedBy="parent") - * @ORM\OrderBy({"name" = "ASC"}) - * @var Collection - */ + #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $children; - /** - * @ORM\ManyToOne(targetEntity="Group", inversedBy="children") - * @ORM\JoinColumn(name="parent_id", referencedColumnName="id") - */ - protected ?AbstractStructuralDBElement $parent; + #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')] + #[ORM\JoinColumn(name: 'parent_id')] + protected ?AbstractStructuralDBElement $parent = null; /** - * @ORM\OneToMany(targetEntity="User", mappedBy="group") * @var Collection */ + #[ORM\OneToMany(targetEntity: User::class, mappedBy: 'group')] protected Collection $users; /** * @var bool If true all users associated with this group must have enabled some kind of two-factor authentication - * @ORM\Column(type="boolean", name="enforce_2fa") - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN, name: 'enforce_2fa')] protected bool $enforce2FA = false; + /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\GroupAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"name" = "ASC"}) - * @Assert\Valid() */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: GroupAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; - /** - * @var PermissionData|null - * @ValidPermission() - * @ORM\Embedded(class="PermissionData", columnPrefix="permissions_") - * @Groups({"full"}) - */ + #[Groups(['full'])] + #[ORM\Embedded(class: PermissionData::class, columnPrefix: 'permissions_')] + #[ValidPermission()] protected ?PermissionData $permissions = null; - /** @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Parameters\GroupParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) - * @Assert\Valid() + /** + * @var Collection */ + #[Assert\Valid] + #[ORM\OneToMany(targetEntity: GroupParameter::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['group' => 'ASC', 'name' => 'ASC'])] protected Collection $parameters; public function __construct() { + $this->attachments = new ArrayCollection(); + $this->parameters = new ArrayCollection(); parent::__construct(); $this->permissions = new PermissionData(); $this->users = new ArrayCollection(); @@ -124,7 +123,7 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa public function getPermissions(): PermissionData { - if ($this->permissions === null) { + if (!$this->permissions instanceof PermissionData) { $this->permissions = new PermissionData(); } diff --git a/src/Entity/UserSystem/PermissionData.php b/src/Entity/UserSystem/PermissionData.php index bac73282..01bb2416 100644 --- a/src/Entity/UserSystem/PermissionData.php +++ b/src/Entity/UserSystem/PermissionData.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\UserSystem; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; /** * This class is used to store the permissions of a user. * This has to be an embeddable or otherwise doctrine could not track the changes of the underlying data array (which is serialized to JSON in the database) - * - * @ORM\Embeddable() + * @see \App\Tests\Entity\UserSystem\PermissionDataTest */ +#[ORM\Embeddable] final class PermissionData implements \JsonSerializable { /** @@ -42,27 +45,22 @@ final class PermissionData implements \JsonSerializable */ public const CURRENT_SCHEMA_VERSION = 2; - /** - * @var array|null This array contains the permission values for each permission - * This array contains the permission values for each permission, in the form of: - * permission => [ - * operation => value, - * ] - * @ORM\Column(type="json", name="data") - */ - protected ?array $data = [ - //$ prefixed entries are used for metadata - '$ver' => self::CURRENT_SCHEMA_VERSION, //The schema version of the permission data - ]; - /** * Creates a new Permission Data Instance using the given data. * By default, an empty array is used, meaning */ - public function __construct(array $data = []) + public function __construct( + /** + * @var array This array contains the permission values for each permission + * This array contains the permission values for each permission, in the form of: + * permission => [ + * operation => value, + * ] + */ + #[ORM\Column(type: Types::JSON, name: 'data')] + protected array $data = [] + ) { - $this->data = $data; - //If the passed data did not contain a schema version, we set it to the current version if (!isset($this->data['$ver'])) { $this->data['$ver'] = self::CURRENT_SCHEMA_VERSION; @@ -71,8 +69,6 @@ final class PermissionData implements \JsonSerializable /** * Checks if any of the operations of the given permission is defined (meaning it is either ALLOW or DENY) - * @param string $permission - * @return bool */ public function isAnyOperationOfPermissionSet(string $permission): bool { @@ -81,7 +77,6 @@ final class PermissionData implements \JsonSerializable /** * Returns an associative array containing all defined (non-INHERIT) operations of the given permission. - * @param string $permission * @return array An array in the form ["operation" => value], returns an empty array if no operations are defined */ public function getAllDefinedOperationsOfPermission(string $permission): array @@ -96,8 +91,6 @@ final class PermissionData implements \JsonSerializable /** * Sets all operations of the given permission via the given array. * The data is an array in the form [$operation => $value], all existing values will be overwritten/deleted. - * @param string $permission - * @param array $data * @return $this */ public function setAllOperationsOfPermission(string $permission, array $data): self @@ -109,7 +102,6 @@ final class PermissionData implements \JsonSerializable /** * Removes a whole permission from the data including all operations (effectivly setting them to INHERIT) - * @param string $permission * @return $this */ public function removePermission(string $permission): self @@ -121,14 +113,12 @@ final class PermissionData implements \JsonSerializable /** * Check if a permission value is set for the given permission and operation (meaning there value is not inherit). - * @param string $permission - * @param string $operation * @return bool True if the permission value is set, false otherwise */ public function isPermissionSet(string $permission, string $operation): bool { //We cannot access metadata via normal permission data - if (strpos($permission, '$') !== false) { + if (str_contains($permission, '$')) { return false; } @@ -137,8 +127,6 @@ final class PermissionData implements \JsonSerializable /** * Returns the permission value for the given permission and operation. - * @param string $permission - * @param string $operation * @return bool|null True means allow, false means disallow, null means inherit */ public function getPermissionValue(string $permission, string $operation): ?bool @@ -153,9 +141,6 @@ final class PermissionData implements \JsonSerializable /** * Sets the permission value for the given permission and operation. - * @param string $permission - * @param string $operation - * @param bool|null $value * @return $this */ public function setPermissionValue(string $permission, string $operation, ?bool $value): self @@ -186,8 +171,6 @@ final class PermissionData implements \JsonSerializable /** * Creates a new Permission Data Instance using the given JSON encoded data - * @param string $json - * @return static * @throws \JsonException */ public static function fromJSON(string $json): self @@ -203,7 +186,6 @@ final class PermissionData implements \JsonSerializable /** * Returns an JSON encodable representation of this object. - * @return array */ public function jsonSerialize(): array { @@ -216,9 +198,7 @@ final class PermissionData implements \JsonSerializable continue; } - $ret[$permission] = array_filter($operations, static function ($value) { - return $value !== null; - }); + $ret[$permission] = array_filter($operations, static fn($value) => $value !== null); //If the permission has no operations, unset it if (empty($ret[$permission])) { @@ -240,7 +220,6 @@ final class PermissionData implements \JsonSerializable /** * Sets the schema version of this permission data - * @param int $new_version * @return $this */ public function setSchemaVersion(int $new_version): self @@ -253,4 +232,4 @@ final class PermissionData implements \JsonSerializable return $this; } -} \ No newline at end of file +} diff --git a/src/Entity/UserSystem/U2FKey.php b/src/Entity/UserSystem/U2FKey.php index 9c1c68a3..f6a2a2e4 100644 --- a/src/Entity/UserSystem/U2FKey.php +++ b/src/Entity/UserSystem/U2FKey.php @@ -22,19 +22,15 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use Doctrine\DBAL\Types\Types; use App\Entity\Base\TimestampTrait; use Doctrine\ORM\Mapping as ORM; use Jbtronics\TFAWebauthn\Model\LegacyU2FKeyInterface; -/** - * @ORM\Entity - * @ORM\Table(name="u2f_keys", - * uniqueConstraints={ - * @ORM\UniqueConstraint(name="user_unique",columns={"user_id", - * "key_handle"}) - * }) - * @ORM\HasLifecycleCallbacks() - */ +#[ORM\Entity] +#[ORM\HasLifecycleCallbacks] +#[ORM\Table(name: 'u2f_keys')] +#[ORM\UniqueConstraint(name: 'user_unique', columns: ['user_id', 'key_handle'])] class U2FKey implements LegacyU2FKeyInterface { use TimestampTrait; @@ -43,50 +39,42 @@ class U2FKey implements LegacyU2FKeyInterface * We have to restrict the length here, as InnoDB only supports key index with max. 767 Bytes. * Max length of keyhandles should be 128. (According to U2F_MAX_KH_SIZE in FIDO example C code). * - * @ORM\Column(type="string", length=128) * * @var string **/ - public string $keyHandle; + #[ORM\Column(type: Types::STRING, length: 128)] + public string $keyHandle = ''; /** - * @ORM\Column(type="string") - * * @var string **/ - public string $publicKey; + #[ORM\Column(type: Types::STRING)] + public string $publicKey = ''; /** - * @ORM\Column(type="text") - * * @var string **/ - public string $certificate; + #[ORM\Column(type: Types::TEXT)] + public string $certificate = ''; /** - * @ORM\Column(type="string") - * - * @var int + * @var string **/ - public int $counter; + #[ORM\Column(type: Types::STRING)] + public string $counter = '0'; - /** - * @ORM\Id - * @ORM\Column(type="integer") - * @ORM\GeneratedValue(strategy="AUTO") - */ + #[ORM\Id] + #[ORM\Column(type: Types::INTEGER)] + #[ORM\GeneratedValue] protected int $id; /** - * @ORM\Column(type="string") - * * @var string **/ - protected string $name; + #[ORM\Column(type: Types::STRING)] + protected string $name = ''; - /** - * @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User", inversedBy="u2fKeys") - **/ + #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'u2fKeys')] protected ?User $user = null; public function getKeyHandle(): string @@ -126,7 +114,7 @@ class U2FKey implements LegacyU2FKeyInterface return $this; } - public function getCounter(): int + public function getCounter(): string { return $this->counter; } @@ -151,9 +139,9 @@ class U2FKey implements LegacyU2FKeyInterface } /** - * Gets the user, this U2F key belongs to. + * Gets the user, this U2F key belongs to. */ - public function getUser(): User + public function getUser(): User|null { return $this->user; } diff --git a/src/Entity/UserSystem/User.php b/src/Entity/UserSystem/User.php index 7c271c6c..0ec54be3 100644 --- a/src/Entity/UserSystem/User.php +++ b/src/Entity/UserSystem/User.php @@ -22,6 +22,10 @@ declare(strict_types=1); namespace App\Entity\UserSystem; +use App\Repository\UserRepository; +use App\EntityListeners\TreeCacheInvalidationListener; +use App\Validator\Constraints\NoLockout; +use Doctrine\DBAL\Types\Types; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\UserAttachment; use App\Entity\Base\AbstractNamedDBElement; @@ -30,8 +34,8 @@ use App\Security\Interfaces\HasPermissionsInterface; use App\Validator\Constraints\Selectable; use App\Validator\Constraints\ValidPermission; use App\Validator\Constraints\ValidTheme; -use Hslavich\OneloginSamlBundle\Security\User\SamlUserInterface; use Jbtronics\TFAWebauthn\Model\LegacyU2FKeyInterface; +use Nbgrp\OneloginSamlBundle\Security\User\SamlUserInterface; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Serializer\Annotation\Groups; use Webauthn\PublicKeyCredentialUserEntity; @@ -54,14 +58,16 @@ use Jbtronics\TFAWebauthn\Model\TwoFactorInterface as WebauthnTwoFactorInterface /** * This entity represents a user, which can log in and have permissions. * Also, this entity is able to save some information about the user, like the names, email-address and other info. + * @see \App\Tests\Entity\UserSystem\UserTest * - * @ORM\Entity(repositoryClass="App\Repository\UserRepository") - * @ORM\Table("`users`", indexes={ - * @ORM\Index(name="user_idx_username", columns={"name"}) - * }) - * @ORM\EntityListeners({"App\EntityListeners\TreeCacheInvalidationListener"}) - * @UniqueEntity("name", message="validator.user.username_already_used") + * @extends AttachmentContainingDBElement */ +#[UniqueEntity('name', message: 'validator.user.username_already_used')] +#[ORM\Entity(repositoryClass: UserRepository::class)] +#[ORM\EntityListeners([TreeCacheInvalidationListener::class])] +#[ORM\Table('`users`')] +#[ORM\Index(name: 'user_idx_username', columns: ['name'])] +#[NoLockout()] class User extends AttachmentContainingDBElement implements UserInterface, HasPermissionsInterface, TwoFactorInterface, BackupCodeInterface, TrustedDeviceInterface, WebauthnTwoFactorInterface, PreferredProviderInterface, PasswordAuthenticatedUserInterface, SamlUserInterface { @@ -70,179 +76,171 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * The User id of the anonymous user. */ - public const ID_ANONYMOUS = 1; + final public const ID_ANONYMOUS = 1; /** * @var bool Determines if the user is disabled (user can not log in) - * @ORM\Column(type="boolean") - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $disabled = false; /** * @var string|null The theme - * @ORM\Column(type="string", name="config_theme", nullable=true) - * @ValidTheme() - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::STRING, name: 'config_theme', nullable: true)] + #[ValidTheme()] protected ?string $theme = null; /** * @var string|null the hash of a token the user must provide when he wants to reset his password - * @ORM\Column(type="string", nullable=true) */ + #[ORM\Column(type: Types::STRING, nullable: true)] protected ?string $pw_reset_token = null; - /** - * @ORM\Column(type="text", name="config_instock_comment_a") - */ + #[ORM\Column(type: Types::TEXT, name: 'config_instock_comment_a')] protected string $instock_comment_a = ''; - /** - * @ORM\Column(type="text", name="config_instock_comment_w") - */ + #[ORM\Column(type: Types::TEXT, name: 'config_instock_comment_w')] protected string $instock_comment_w = ''; /** * @var string A self-description of the user - * @ORM\Column(type="text") - * @Groups({"full", "import"}) */ + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::TEXT)] protected string $aboutMe = ''; /** @var int The version of the trusted device cookie. Used to invalidate all trusted device cookies at once. - * @ORM\Column(type="integer") */ + #[ORM\Column(type: Types::INTEGER)] protected int $trustedDeviceCookieVersion = 0; /** * @var string[]|null A list of backup codes that can be used, if the user has no access to its Google Authenticator device - * @ORM\Column(type="json") */ + #[ORM\Column(type: Types::JSON)] protected ?array $backupCodes = []; - /** - * @ORM\Id() - * @ORM\GeneratedValue() - * @ORM\Column(type="integer") - */ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column(type: Types::INTEGER)] protected ?int $id = null; /** * @var Group|null the group this user belongs to * DO NOT PUT A fetch eager here! Otherwise, you can not unset the group of a user! This seems to be some kind of bug in doctrine. Maybe this is fixed in future versions. - * @ORM\ManyToOne(targetEntity="Group", inversedBy="users") - * @ORM\JoinColumn(name="group_id", referencedColumnName="id") - * @Selectable() - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\ManyToOne(targetEntity: Group::class, inversedBy: 'users')] + #[ORM\JoinColumn(name: 'group_id')] + #[Selectable] protected ?Group $group = null; /** * @var string|null The secret used for Google authenticator - * @ORM\Column(name="google_authenticator_secret", type="string", nullable=true) */ + #[ORM\Column(name: 'google_authenticator_secret', type: Types::STRING, nullable: true)] protected ?string $googleAuthenticatorSecret = null; /** * @var string|null The timezone the user prefers - * @ORM\Column(type="string", name="config_timezone", nullable=true) - * @Assert\Timezone() - * @Groups({"full", "import"}) */ + #[Assert\Timezone] + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::STRING, name: 'config_timezone', nullable: true)] protected ?string $timezone = ''; /** * @var string|null The language/locale the user prefers - * @ORM\Column(type="string", name="config_language", nullable=true) - * @Assert\Language() - * @Groups({"full", "import"}) */ + #[Assert\Language] + #[Groups(['full', 'import'])] + #[ORM\Column(type: Types::STRING, name: 'config_language', nullable: true)] protected ?string $language = ''; /** * @var string|null The email address of the user - * @ORM\Column(type="string", length=255, nullable=true) - * @Assert\Email() - * @Groups({"simple", "extended", "full", "import"}) */ + #[Assert\Email] + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] protected ?string $email = ''; /** * @var bool True if the user wants to show his email address on his (public) profile - * @ORM\Column(type="boolean", options={"default": false}) */ + #[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])] protected bool $show_email_on_profile = false; /** * @var string|null The department the user is working - * @ORM\Column(type="string", length=255, nullable=true) - * @Groups({"simple", "extended", "full", "import"}) */ + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] protected ?string $department = ''; /** * @var string|null The last name of the User - * @ORM\Column(type="string", length=255, nullable=true) - * @Groups({"simple", "extended", "full", "import"}) */ + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] protected ?string $last_name = ''; /** * @var string|null The first name of the User - * @ORM\Column(type="string", length=255, nullable=true) - * @Groups({"simple", "extended", "full", "import"}) */ + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\Column(type: Types::STRING, length: 255, nullable: true)] protected ?string $first_name = ''; /** * @var bool True if the user needs to change password after log in - * @ORM\Column(type="boolean") - * @Groups({"extended", "full", "import"}) */ + #[Groups(['extended', 'full', 'import'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $need_pw_change = true; /** * @var string|null The hashed password - * @ORM\Column(type="string", nullable=true) */ + #[ORM\Column(type: Types::STRING, nullable: true)] protected ?string $password = null; - /** - * @ORM\Column(type="string", length=180, unique=true) - * @Assert\NotBlank - * @Assert\Regex("/^[\w\.\+\-\$]+$/", message="user.invalid_username") - */ + #[Assert\NotBlank] + #[Assert\Regex('/^[\w\.\+\-\$]+$/', message: 'user.invalid_username')] + #[ORM\Column(type: Types::STRING, length: 180, unique: true)] protected string $name = ''; /** * @var array|null - * @ORM\Column(type="json") */ + #[ORM\Column(type: Types::JSON)] protected ?array $settings = []; /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\UserAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) - * @ORM\OrderBy({"name" = "ASC"}) */ + #[ORM\OneToMany(targetEntity: UserAttachment::class, mappedBy: 'element', cascade: ['persist', 'remove'], orphanRemoval: true)] + #[ORM\OrderBy(['name' => 'ASC'])] protected Collection $attachments; - /** @var DateTime|null The time when the backup codes were generated - * @ORM\Column(type="datetime", nullable=true) - * @Groups({"full"}) + /** @var \DateTimeInterface|null The time when the backup codes were generated */ - protected ?DateTime $backupCodesGenerationDate = null; + #[Groups(['full'])] + #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] + protected ?\DateTimeInterface $backupCodesGenerationDate = null; /** @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\UserSystem\U2FKey", mappedBy="user", cascade={"REMOVE"}, orphanRemoval=true) */ + #[ORM\OneToMany(targetEntity: U2FKey::class, mappedBy: 'user', cascade: ['REMOVE'], orphanRemoval: true)] protected Collection $u2fKeys; /** * @var Collection - * @ORM\OneToMany(targetEntity="App\Entity\UserSystem\WebauthnKey", mappedBy="user", cascade={"REMOVE"}, orphanRemoval=true) */ + #[ORM\OneToMany(targetEntity: WebauthnKey::class, mappedBy: 'user', cascade: ['REMOVE'], orphanRemoval: true)] protected Collection $webauthn_keys; /** @@ -250,36 +248,34 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * Dont use fetch=EAGER here, this will cause problems with setting the currency setting. * TODO: This is most likely a bug in doctrine/symfony related to the UniqueEntity constraint (it makes a db call). * TODO: Find a way to use fetch EAGER (this improves performance a bit) - * @ORM\ManyToOne(targetEntity="App\Entity\PriceInformations\Currency") - * @ORM\JoinColumn(name="currency_id", referencedColumnName="id") - * @Selectable() - * @Groups({"extended", "full", "import"}) */ - protected ?Currency $currency; + #[Groups(['extended', 'full', 'import'])] + #[ORM\ManyToOne(targetEntity: Currency::class)] + #[ORM\JoinColumn(name: 'currency_id')] + #[Selectable] + protected ?Currency $currency = null; - /** - * @var PermissionData|null - * @ValidPermission() - * @ORM\Embedded(class="PermissionData", columnPrefix="permissions_") - * @Groups({"simple", "extended", "full", "import"}) - */ + #[Groups(['simple', 'extended', 'full', 'import'])] + #[ORM\Embedded(class: 'PermissionData', columnPrefix: 'permissions_')] + #[ValidPermission()] protected ?PermissionData $permissions = null; /** - * @var DateTime|null the time until the password reset token is valid - * @ORM\Column(type="datetime", nullable=true, options={"default": null}) + * @var \DateTimeInterface|null the time until the password reset token is valid */ - protected ?DateTime $pw_reset_expires; + #[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)] + protected ?\DateTimeInterface $pw_reset_expires = null; /** * @var bool True if the user was created by a SAML provider (and therefore cannot change its password) - * @ORM\Column(type="boolean") - * @Groups({"extended", "full"}) */ + #[Groups(['extended', 'full'])] + #[ORM\Column(type: Types::BOOLEAN)] protected bool $saml_user = false; public function __construct() { + $this->attachments = new ArrayCollection(); parent::__construct(); $this->permissions = new PermissionData(); $this->u2fKeys = new ArrayCollection(); @@ -292,7 +288,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * * @return string */ - public function __toString() + public function __toString(): string { $tmp = $this->isDisabled() ? ' [DISABLED]' : ''; @@ -359,8 +355,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Sets the password hash for this user. - * - * @return User */ public function setPassword(string $password): self { @@ -398,8 +392,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Sets the currency the users prefers to see prices in. - * - * @return User */ public function setCurrency(?Currency $currency): self { @@ -422,8 +414,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * Sets the status if a user is disabled. * * @param bool $disabled true if the user should be disabled - * - * @return User */ public function setDisabled(bool $disabled): self { @@ -434,7 +424,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe public function getPermissions(): PermissionData { - if ($this->permissions === null) { + if (!$this->permissions instanceof PermissionData) { $this->permissions = new PermissionData(); } @@ -451,8 +441,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Set the status, if the user needs a password change. - * - * @return User */ public function setNeedPwChange(bool $need_pw_change): self { @@ -471,8 +459,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Sets the encrypted password reset token. - * - * @return User */ public function setPwResetToken(?string $pw_reset_token): self { @@ -482,19 +468,17 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe } /** - * Gets the datetime when the password reset token expires. + * Gets the datetime when the password reset token expires. */ - public function getPwResetExpires(): DateTime + public function getPwResetExpires(): \DateTimeInterface|null { return $this->pw_reset_expires; } /** * Sets the datetime when the password reset token expires. - * - * @return User */ - public function setPwResetExpires(DateTime $pw_reset_expires): self + public function setPwResetExpires(\DateTimeInterface $pw_reset_expires): self { $this->pw_reset_expires = $pw_reset_expires; @@ -517,7 +501,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe { $tmp = $this->getFirstName(); //Don't add a space, if the name has only one part (it would look strange) - if (!empty($this->getFirstName()) && !empty($this->getLastName())) { + if ($this->getFirstName() !== null && $this->getFirstName() !== '' && ($this->getLastName() !== null && $this->getLastName() !== '')) { $tmp .= ' '; } $tmp .= $this->getLastName(); @@ -604,8 +588,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * Change the department of the user. * * @param string|null $department The new department - * - * @return User */ public function setDepartment(?string $department): self { @@ -640,7 +622,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Gets whether the email address of the user is shown on the public profile page. - * @return bool */ public function isShowEmailOnProfile(): bool { @@ -649,8 +630,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Sets whether the email address of the user is shown on the public profile page. - * @param bool $show_email_on_profile - * @return User */ public function setShowEmailOnProfile(bool $show_email_on_profile): User { @@ -662,7 +641,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Returns the about me text of the user. - * @return string */ public function getAboutMe(): string { @@ -671,8 +649,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Change the about me text of the user. - * @param string $aboutMe - * @return User */ public function setAboutMe(string $aboutMe): User { @@ -698,8 +674,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe * * @param string|null $language The new language as 2-letter ISO code (e.g. 'en' or 'de'). * Set to null, to use the system-wide language. - * - * @return User */ public function setLanguage(?string $language): self { @@ -864,11 +838,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe public function setBackupCodes(array $codes): self { $this->backupCodes = $codes; - if (empty($codes)) { - $this->backupCodesGenerationDate = null; - } else { - $this->backupCodesGenerationDate = new DateTime(); - } + $this->backupCodesGenerationDate = $codes === [] ? null : new DateTime(); return $this; } @@ -876,7 +846,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Return the date when the backup codes were generated. */ - public function getBackupCodesGenerationDate(): ?DateTime + public function getBackupCodesGenerationDate(): ?\DateTimeInterface { return $this->backupCodesGenerationDate; } @@ -930,7 +900,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe { return new PublicKeyCredentialUserEntity( $this->getUsername(), - (string) $this->getId(), + (string) $this->getID(), $this->getFullName(), ); } @@ -947,7 +917,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Returns true, if the user was created by the SAML authentication. - * @return bool */ public function isSamlUser(): bool { @@ -956,8 +925,6 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe /** * Sets the saml_user flag. - * @param bool $saml_user - * @return User */ public function setSamlUser(bool $saml_user): User { @@ -967,7 +934,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe - public function setSamlAttributes(array $attributes) + public function setSamlAttributes(array $attributes): void { //When mail attribute exists, set it if (isset($attributes['email'])) { diff --git a/src/Entity/UserSystem/WebauthnKey.php b/src/Entity/UserSystem/WebauthnKey.php index 5d5c654d..ee467bc3 100644 --- a/src/Entity/UserSystem/WebauthnKey.php +++ b/src/Entity/UserSystem/WebauthnKey.php @@ -1,4 +1,7 @@ . */ - namespace App\Entity\UserSystem; +use Doctrine\DBAL\Types\Types; use App\Entity\Base\TimestampTrait; use Doctrine\ORM\Mapping as ORM; +use Symfony\Component\Validator\Constraints\NotBlank; use Webauthn\PublicKeyCredentialSource as BasePublicKeyCredentialSource; -/** - * @ORM\Table(name="webauthn_keys") - * @ORM\Entity() - * @ORM\HasLifecycleCallbacks() - */ +#[ORM\Entity] +#[ORM\HasLifecycleCallbacks] +#[ORM\Table(name: 'webauthn_keys')] class WebauthnKey extends BasePublicKeyCredentialSource { use TimestampTrait; - /** - * @ORM\Id - * @ORM\Column(type="integer") - * @ORM\GeneratedValue(strategy="AUTO") - */ + #[ORM\Id] + #[ORM\Column(type: Types::INTEGER)] + #[ORM\GeneratedValue] protected int $id; - /** - * @ORM\Column(type="string") - */ - protected string $name; + #[ORM\Column(type: Types::STRING)] + #[NotBlank] + protected string $name = ''; - /** - * @ORM\ManyToOne(targetEntity="App\Entity\UserSystem\User", inversedBy="webauthn_keys") - **/ + #[ORM\ManyToOne(targetEntity: User::class, inversedBy: 'webauthn_keys')] protected ?User $user = null; - /** - * @return string - */ public function getName(): string { return $this->name; } - /** - * @param string $name - * @return WebauthnKey - */ public function setName(string $name): WebauthnKey { $this->name = $name; return $this; } - /** - * @return User|null - */ public function getUser(): ?User { return $this->user; } - /** - * @param User|null $user - * @return WebauthnKey - */ public function setUser(?User $user): WebauthnKey { $this->user = $user; return $this; } - /** - * @return int - */ public function getId(): int { return $this->id; @@ -113,4 +93,4 @@ class WebauthnKey extends BasePublicKeyCredentialSource $registration->getOtherUI() ); } -} \ No newline at end of file +} diff --git a/src/EntityListeners/AttachmentDeleteListener.php b/src/EntityListeners/AttachmentDeleteListener.php index 84d588c7..e9df5972 100644 --- a/src/EntityListeners/AttachmentDeleteListener.php +++ b/src/EntityListeners/AttachmentDeleteListener.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\EntityListeners; +use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\Attachment; use App\Services\Attachments\AttachmentManager; use App\Services\Attachments\AttachmentPathResolver; @@ -41,22 +42,14 @@ use SplFileInfo; */ class AttachmentDeleteListener { - protected AttachmentReverseSearch $attachmentReverseSearch; - protected AttachmentManager $attachmentHelper; - protected AttachmentPathResolver $pathResolver; - - public function __construct(AttachmentReverseSearch $attachmentReverseSearch, AttachmentManager $attachmentHelper, AttachmentPathResolver $pathResolver) + public function __construct(protected AttachmentReverseSearch $attachmentReverseSearch, protected AttachmentManager $attachmentHelper, protected AttachmentPathResolver $pathResolver) { - $this->attachmentReverseSearch = $attachmentReverseSearch; - $this->attachmentHelper = $attachmentHelper; - $this->pathResolver = $pathResolver; } /** * Removes the file associated with the attachment, if the file associated with the attachment changes. - * - * @PreUpdate */ + #[PreUpdate] public function preUpdateHandler(Attachment $attachment, PreUpdateEventArgs $event): void { if ($event->hasChangedField('path')) { @@ -81,15 +74,14 @@ class AttachmentDeleteListener /** * Ensure that attachments are not used in preview, so that they can be deleted (without integrity violation). - * - * @ORM\PreRemove() */ + #[ORM\PreRemove] public function preRemoveHandler(Attachment $attachment, PreRemoveEventArgs $event): void { //Ensure that the attachment that will be deleted, is not used as preview picture anymore... $attachment_holder = $attachment->getElement(); - if (null === $attachment_holder) { + if (!$attachment_holder instanceof AttachmentContainingDBElement) { return; } @@ -102,16 +94,15 @@ class AttachmentDeleteListener if (!$em instanceof EntityManagerInterface) { throw new \RuntimeException('Invalid EntityManagerInterface!'); } - $classMetadata = $em->getClassMetadata(get_class($attachment_holder)); + $classMetadata = $em->getClassMetadata($attachment_holder::class); $em->getUnitOfWork()->computeChangeSet($classMetadata, $attachment_holder); } } /** * Removes the file associated with the attachment, after the attachment was deleted. - * - * @PostRemove */ + #[PostRemove] public function postRemoveHandler(Attachment $attachment, PostRemoveEventArgs $event): void { //Dont delete file if the attachment uses a builtin ressource: @@ -121,7 +112,7 @@ class AttachmentDeleteListener $file = $this->attachmentHelper->attachmentToFile($attachment); //Only delete if the attachment has a valid file. - if (null !== $file) { + if ($file instanceof \SplFileInfo) { /* The original file has already been removed, so we have to decrease the threshold to zero, as any remaining attachment depends on this attachment, and we must not delete this file! */ $this->attachmentReverseSearch->deleteIfNotUsed($file, 0); diff --git a/src/EntityListeners/TreeCacheInvalidationListener.php b/src/EntityListeners/TreeCacheInvalidationListener.php index 017c8018..4cbcf8f8 100644 --- a/src/EntityListeners/TreeCacheInvalidationListener.php +++ b/src/EntityListeners/TreeCacheInvalidationListener.php @@ -35,25 +35,18 @@ use Symfony\Contracts\Cache\TagAwareCacheInterface; class TreeCacheInvalidationListener { - protected TagAwareCacheInterface $cache; - protected UserCacheKeyGenerator $keyGenerator; - - public function __construct(TagAwareCacheInterface $treeCache, UserCacheKeyGenerator $keyGenerator) + public function __construct(protected TagAwareCacheInterface $cache, protected UserCacheKeyGenerator $keyGenerator) { - $this->cache = $treeCache; - $this->keyGenerator = $keyGenerator; } - /** - * @ORM\PostUpdate() - * @ORM\PostPersist() - * @ORM\PostRemove() - */ + #[ORM\PostUpdate] + #[ORM\PostPersist] + #[ORM\PostRemove] public function invalidate(AbstractDBElement $element, LifecycleEventArgs $event): void { //If an element was changed, then invalidate all cached trees with this element class if ($element instanceof AbstractStructuralDBElement || $element instanceof LabelProfile) { - $secure_class_name = str_replace('\\', '_', get_class($element)); + $secure_class_name = str_replace('\\', '_', $element::class); $this->cache->invalidateTags([$secure_class_name]); //Trigger a sidebar reload for all users (see SidebarTreeUpdater service) @@ -64,7 +57,7 @@ class TreeCacheInvalidationListener //If a user change, then invalidate all cached trees for him if ($element instanceof User) { - $secure_class_name = str_replace('\\', '_', get_class($element)); + $secure_class_name = str_replace('\\', '_', $element::class); $tag = $this->keyGenerator->generateKey($element); $this->cache->invalidateTags([$tag, $secure_class_name]); } diff --git a/src/EventSubscriber/LogSystem/EventLoggerSubscriber.php b/src/EventSubscriber/LogSystem/EventLoggerSubscriber.php index eed5953d..262f196d 100644 --- a/src/EventSubscriber/LogSystem/EventLoggerSubscriber.php +++ b/src/EventSubscriber/LogSystem/EventLoggerSubscriber.php @@ -70,30 +70,12 @@ class EventLoggerSubscriber implements EventSubscriber ]; protected const MAX_STRING_LENGTH = 2000; - - protected EventLogger $logger; - protected SerializerInterface $serializer; - protected EventCommentHelper $eventCommentHelper; - protected EventUndoHelper $eventUndoHelper; - protected bool $save_changed_fields; - protected bool $save_changed_data; - protected bool $save_removed_data; protected bool $save_new_data; - protected PropertyAccessorInterface $propertyAccessor; - public function __construct(EventLogger $logger, SerializerInterface $serializer, EventCommentHelper $commentHelper, - bool $save_changed_fields, bool $save_changed_data, bool $save_removed_data, bool $save_new_data, - PropertyAccessorInterface $propertyAccessor, EventUndoHelper $eventUndoHelper) + public function __construct(protected EventLogger $logger, protected SerializerInterface $serializer, protected EventCommentHelper $eventCommentHelper, + protected bool $save_changed_fields, protected bool $save_changed_data, protected bool $save_removed_data, bool $save_new_data, + protected PropertyAccessorInterface $propertyAccessor, protected EventUndoHelper $eventUndoHelper) { - $this->logger = $logger; - $this->serializer = $serializer; - $this->eventCommentHelper = $commentHelper; - $this->propertyAccessor = $propertyAccessor; - $this->eventUndoHelper = $eventUndoHelper; - - $this->save_changed_fields = $save_changed_fields; - $this->save_changed_data = $save_changed_data; - $this->save_removed_data = $save_removed_data; //This option only makes sense if save_changed_data is true $this->save_new_data = $save_new_data && $save_changed_data; } @@ -164,7 +146,7 @@ class EventLoggerSubscriber implements EventSubscriber $uow = $em->getUnitOfWork(); // If we have added any ElementCreatedLogEntries added in postPersist, we flush them here. $uow->computeChangeSets(); - if ($uow->hasPendingInsertions() || !empty($uow->getScheduledEntityUpdates())) { + if ($uow->hasPendingInsertions() || $uow->getScheduledEntityUpdates() !== []) { $em->flush(); } @@ -181,7 +163,7 @@ class EventLoggerSubscriber implements EventSubscriber public function hasFieldRestrictions(AbstractDBElement $element): bool { foreach (array_keys(static::FIELD_BLACKLIST) as $class) { - if (is_a($element, $class)) { + if ($element instanceof $class) { return true; } } @@ -195,7 +177,7 @@ class EventLoggerSubscriber implements EventSubscriber public function shouldFieldBeSaved(AbstractDBElement $element, string $field_name): bool { foreach (static::FIELD_BLACKLIST as $class => $blacklist) { - if (is_a($element, $class) && in_array($field_name, $blacklist, true)) { + if ($element instanceof $class && in_array($field_name, $blacklist, true)) { return false; } } @@ -231,11 +213,11 @@ class EventLoggerSubscriber implements EventSubscriber //Check if we have to log CollectionElementDeleted entries if ($this->save_changed_data) { - $metadata = $em->getClassMetadata(get_class($entity)); + $metadata = $em->getClassMetadata($entity::class); $mappings = $metadata->getAssociationMappings(); //Check if class is whitelisted for CollectionElementDeleted entry foreach (static::TRIGGER_ASSOCIATION_LOG_WHITELIST as $class => $whitelist) { - if (is_a($entity, $class)) { + if ($entity instanceof $class) { //Check names foreach ($mappings as $field => $mapping) { if (in_array($field, $whitelist, true)) { @@ -308,8 +290,6 @@ class EventLoggerSubscriber implements EventSubscriber /** * Restrict the length of every string in the given array to MAX_STRING_LENGTH, to save memory in the case of very * long strings (e.g. images in notes) - * @param array $fields - * @return array */ protected function fieldLengthRestrict(array $fields): array { @@ -325,6 +305,7 @@ class EventLoggerSubscriber implements EventSubscriber protected function saveChangeSet(AbstractDBElement $entity, AbstractLogEntry $logEntry, EntityManagerInterface $em, bool $element_deleted = false): void { + $new_data = null; $uow = $em->getUnitOfWork(); if (!$logEntry instanceof ElementEditedLogEntry && !$logEntry instanceof ElementDeletedLogEntry) { @@ -348,7 +329,7 @@ class EventLoggerSubscriber implements EventSubscriber $logEntry->setOldData($old_data); - if (!empty($new_data)) { + if ($new_data !== []) { $new_data = $this->filterFieldRestrictions($entity, $new_data); $new_data = $this->fieldLengthRestrict($new_data); diff --git a/src/EventSubscriber/LogSystem/LogAccessDeniedSubscriber.php b/src/EventSubscriber/LogSystem/LogAccessDeniedSubscriber.php index 45dbe61b..abe2f9ba 100644 --- a/src/EventSubscriber/LogSystem/LogAccessDeniedSubscriber.php +++ b/src/EventSubscriber/LogSystem/LogAccessDeniedSubscriber.php @@ -53,11 +53,8 @@ use Symfony\Component\Security\Core\Exception\AccessDeniedException; */ class LogAccessDeniedSubscriber implements EventSubscriberInterface { - private EventLogger $logger; - - public function __construct(EventLogger $logger) + public function __construct(private readonly EventLogger $logger) { - $this->logger = $logger; } public function onKernelException(ExceptionEvent $event): void diff --git a/src/EventSubscriber/LogSystem/LogDBMigrationSubscriber.php b/src/EventSubscriber/LogSystem/LogDBMigrationSubscriber.php index 610c0b5e..07ec8ea4 100644 --- a/src/EventSubscriber/LogSystem/LogDBMigrationSubscriber.php +++ b/src/EventSubscriber/LogSystem/LogDBMigrationSubscriber.php @@ -37,13 +37,8 @@ class LogDBMigrationSubscriber implements EventSubscriber protected ?string $old_version = null; protected ?string $new_version = null; - protected EventLogger $eventLogger; - protected DependencyFactory $dependencyFactory; - - public function __construct(EventLogger $eventLogger, DependencyFactory $dependencyFactory) + public function __construct(protected EventLogger $eventLogger, protected DependencyFactory $dependencyFactory) { - $this->eventLogger = $eventLogger; - $this->dependencyFactory = $dependencyFactory; } public function onMigrationsMigrated(MigrationsEventArgs $args): void @@ -60,13 +55,13 @@ class LogDBMigrationSubscriber implements EventSubscriber $this->new_version = (string) $aliasResolver->resolveVersionAlias('current'); //After everything is done, write the results to DB log - $this->old_version = empty($this->old_version) ? 'legacy/empty' : $this->old_version; - $this->new_version = empty($this->new_version) ? 'unknown' : $this->new_version; + $this->old_version = $this->old_version === null || $this->old_version === '' ? 'legacy/empty' : $this->old_version; + $this->new_version = $this->new_version === '' ? 'unknown' : $this->new_version; try { $log = new DatabaseUpdatedLogEntry($this->old_version, $this->new_version); $this->eventLogger->logAndFlush($log); - } catch (\Throwable $exception) { + } catch (\Throwable) { //Ignore any exception occuring here... } } diff --git a/src/EventSubscriber/LogSystem/LogoutLoggerListener.php b/src/EventSubscriber/LogSystem/LogLogoutEventSubscriber.php similarity index 68% rename from src/EventSubscriber/LogSystem/LogoutLoggerListener.php rename to src/EventSubscriber/LogSystem/LogLogoutEventSubscriber.php index 3b197b38..87d97b1e 100644 --- a/src/EventSubscriber/LogSystem/LogoutLoggerListener.php +++ b/src/EventSubscriber/LogSystem/LogLogoutEventSubscriber.php @@ -22,35 +22,33 @@ declare(strict_types=1); namespace App\EventSubscriber\LogSystem; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use App\Entity\LogSystem\UserLogoutLogEntry; use App\Entity\UserSystem\User; use App\Services\LogSystem\EventLogger; +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; use Symfony\Component\Security\Http\Event\LogoutEvent; /** * This handler logs to event log, if a user logs out. */ -class LogoutLoggerListener +final class LogLogoutEventSubscriber implements EventSubscriberInterface { - protected EventLogger $logger; - protected bool $gpdr_compliance; - - public function __construct(EventLogger $logger, bool $gpdr_compliance) + public function __construct(private readonly EventLogger $logger, private readonly bool $gdpr_compliance) { - $this->logger = $logger; - $this->gpdr_compliance = $gpdr_compliance; } - public function __invoke(LogoutEvent $event) + public function logLogout(LogoutEvent $event): void { $request = $event->getRequest(); $token = $event->getToken(); - if (null === $token) { + if (!$token instanceof TokenInterface) { return; } - $log = new UserLogoutLogEntry($request->getClientIp(), $this->gpdr_compliance); + $log = new UserLogoutLogEntry($request->getClientIp(), $this->gdpr_compliance); $user = $token->getUser(); if ($user instanceof User) { $log->setTargetElement($user); @@ -58,4 +56,12 @@ class LogoutLoggerListener $this->logger->logAndFlush($log); } + /** + * @return array + */ + public static function getSubscribedEvents(): array + { + return [LogoutEvent::class => 'logLogout']; + } + } diff --git a/src/EventSubscriber/LogSystem/SecurityEventLoggerSubscriber.php b/src/EventSubscriber/LogSystem/SecurityEventLoggerSubscriber.php index 88eeef2c..78941cd5 100644 --- a/src/EventSubscriber/LogSystem/SecurityEventLoggerSubscriber.php +++ b/src/EventSubscriber/LogSystem/SecurityEventLoggerSubscriber.php @@ -41,6 +41,7 @@ declare(strict_types=1); namespace App\EventSubscriber\LogSystem; +use Symfony\Component\HttpFoundation\Request; use App\Entity\LogSystem\SecurityEventLogEntry; use App\Events\SecurityEvent; use App\Events\SecurityEvents; @@ -53,15 +54,8 @@ use Symfony\Component\HttpFoundation\RequestStack; */ final class SecurityEventLoggerSubscriber implements EventSubscriberInterface { - private RequestStack $requestStack; - private bool $gpdr_compliant; - private EventLogger $eventLogger; - - public function __construct(RequestStack $requestStack, EventLogger $eventLogger, bool $gpdr_compliance) + public function __construct(private readonly RequestStack $requestStack, private readonly EventLogger $eventLogger, private readonly bool $gdpr_compliance) { - $this->requestStack = $requestStack; - $this->gpdr_compliant = $gpdr_compliance; - $this->eventLogger = $eventLogger; } public static function getSubscribedEvents(): array @@ -126,10 +120,10 @@ final class SecurityEventLoggerSubscriber implements EventSubscriberInterface private function addLog(string $type, SecurityEvent $event): void { - $anonymize = $this->gpdr_compliant; + $anonymize = $this->gdpr_compliance; $request = $this->requestStack->getCurrentRequest(); - if (null !== $request) { + if ($request instanceof Request) { $ip = $request->getClientIp() ?? 'unknown'; } else { $ip = 'Console'; diff --git a/src/EventSubscriber/SetMailFromSubscriber.php b/src/EventSubscriber/SetMailFromSubscriber.php index 6c9f563c..3675c487 100644 --- a/src/EventSubscriber/SetMailFromSubscriber.php +++ b/src/EventSubscriber/SetMailFromSubscriber.php @@ -32,13 +32,8 @@ use Symfony\Component\Mime\Email; */ final class SetMailFromSubscriber implements EventSubscriberInterface { - private string $email; - private string $name; - - public function __construct(string $email, string $name) + public function __construct(private readonly string $email, private readonly string $name) { - $this->email = $email; - $this->name = $name; } public function onMessage(MessageEvent $event): void diff --git a/src/EventSubscriber/SymfonyDebugToolbarSubscriber.php b/src/EventSubscriber/SymfonyDebugToolbarSubscriber.php index d2aab142..6f17e399 100644 --- a/src/EventSubscriber/SymfonyDebugToolbarSubscriber.php +++ b/src/EventSubscriber/SymfonyDebugToolbarSubscriber.php @@ -30,11 +30,8 @@ use Symfony\Component\HttpKernel\Event\ResponseEvent; */ final class SymfonyDebugToolbarSubscriber implements EventSubscriberInterface { - private bool $kernel_debug; - - public function __construct(bool $kernel_debug) + public function __construct(private readonly bool $kernel_debug_enabled) { - $this->kernel_debug = $kernel_debug; } /** @@ -62,7 +59,7 @@ final class SymfonyDebugToolbarSubscriber implements EventSubscriberInterface public function onKernelResponse(ResponseEvent $event): void { - if (!$this->kernel_debug) { + if (!$this->kernel_debug_enabled) { return; } diff --git a/src/EventSubscriber/UserSystem/LoginSuccessSubscriber.php b/src/EventSubscriber/UserSystem/LoginSuccessSubscriber.php index cce88d82..d67a8e14 100644 --- a/src/EventSubscriber/UserSystem/LoginSuccessSubscriber.php +++ b/src/EventSubscriber/UserSystem/LoginSuccessSubscriber.php @@ -37,23 +37,14 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ final class LoginSuccessSubscriber implements EventSubscriberInterface { - private TranslatorInterface $translator; - private RequestStack $requestStack; - private EventLogger $eventLogger; - private bool $gpdr_compliance; - - public function __construct(TranslatorInterface $translator, RequestStack $requestStack, EventLogger $eventLogger, bool $gpdr_compliance) + public function __construct(private readonly TranslatorInterface $translator, private readonly RequestStack $requestStack, private readonly EventLogger $eventLogger, private readonly bool $gdpr_compliance) { - $this->translator = $translator; - $this->requestStack = $requestStack; - $this->eventLogger = $eventLogger; - $this->gpdr_compliance = $gpdr_compliance; } public function onLogin(InteractiveLoginEvent $event): void { $ip = $event->getRequest()->getClientIp(); - $log = new UserLoginLogEntry($ip, $this->gpdr_compliance); + $log = new UserLoginLogEntry($ip, $this->gdpr_compliance); $user = $event->getAuthenticationToken()->getUser(); if ($user instanceof User && $user->getID()) { $log->setTargetElement($user); diff --git a/src/EventSubscriber/UserSystem/LogoutDisabledUserSubscriber.php b/src/EventSubscriber/UserSystem/LogoutDisabledUserSubscriber.php index a668828c..33c5e919 100644 --- a/src/EventSubscriber/UserSystem/LogoutDisabledUserSubscriber.php +++ b/src/EventSubscriber/UserSystem/LogoutDisabledUserSubscriber.php @@ -22,13 +22,13 @@ declare(strict_types=1); namespace App\EventSubscriber\UserSystem; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\UserSystem\User; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Security\Core\Security; /** * This subscriber is used to log out a disabled user, as soon as he to do a request. @@ -36,14 +36,8 @@ use Symfony\Component\Security\Core\Security; */ final class LogoutDisabledUserSubscriber implements EventSubscriberInterface { - private Security $security; - private UrlGeneratorInterface $urlGenerator; - - public function __construct(Security $security, UrlGeneratorInterface $urlGenerator) + public function __construct(private readonly Security $security, private readonly UrlGeneratorInterface $urlGenerator) { - $this->security = $security; - - $this->urlGenerator = $urlGenerator; } public function onRequest(RequestEvent $event): void diff --git a/src/EventSubscriber/UserSystem/PasswordChangeNeededSubscriber.php b/src/EventSubscriber/UserSystem/PasswordChangeNeededSubscriber.php index ef7da6a9..0f12c6d9 100644 --- a/src/EventSubscriber/UserSystem/PasswordChangeNeededSubscriber.php +++ b/src/EventSubscriber/UserSystem/PasswordChangeNeededSubscriber.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\EventSubscriber\UserSystem; +use Symfony\Bundle\SecurityBundle\Security; +use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; @@ -29,7 +31,6 @@ use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Http\HttpUtils; /** @@ -55,13 +56,9 @@ final class PasswordChangeNeededSubscriber implements EventSubscriberInterface * @var string The route the user will redirected to, if he needs to change this password */ public const REDIRECT_TARGET = 'user_settings'; - private Security $security; - private HttpUtils $httpUtils; - public function __construct(Security $security, HttpUtils $httpUtils) + public function __construct(private readonly Security $security, private readonly HttpUtils $httpUtils) { - $this->security = $security; - $this->httpUtils = $httpUtils; } /** @@ -96,7 +93,7 @@ final class PasswordChangeNeededSubscriber implements EventSubscriberInterface /* Dont redirect tree endpoints, as this would cause trouble and creates multiple flash warnigs for one page reload */ - if (false !== strpos($request->getUri(), '/tree/')) { + if (str_contains($request->getUri(), '/tree/')) { return; } @@ -129,7 +126,7 @@ final class PasswordChangeNeededSubscriber implements EventSubscriberInterface { $tfa_enabled = $user->isWebAuthnAuthenticatorEnabled() || $user->isGoogleAuthenticatorEnabled(); - return null !== $user->getGroup() && $user->getGroup()->isEnforce2FA() && !$tfa_enabled; + return $user->getGroup() instanceof Group && $user->getGroup()->isEnforce2FA() && !$tfa_enabled; } public static function getSubscribedEvents(): array diff --git a/src/EventSubscriber/UserSystem/SetUserTimezoneSubscriber.php b/src/EventSubscriber/UserSystem/SetUserTimezoneSubscriber.php index c39d4b37..10ecaddf 100644 --- a/src/EventSubscriber/UserSystem/SetUserTimezoneSubscriber.php +++ b/src/EventSubscriber/UserSystem/SetUserTimezoneSubscriber.php @@ -23,23 +23,18 @@ declare(strict_types=1); namespace App\EventSubscriber\UserSystem; use App\Entity\UserSystem\User; +use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ControllerEvent; use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\Security\Core\Security; /** * The purpose of this event listener is to set the timezone to the one preferred by the user. */ final class SetUserTimezoneSubscriber implements EventSubscriberInterface { - private string $default_timezone; - private Security $security; - - public function __construct(string $timezone, Security $security) + public function __construct(private readonly string $default_timezone, private readonly Security $security) { - $this->default_timezone = $timezone; - $this->security = $security; } public function setTimeZone(ControllerEvent $event): void @@ -48,12 +43,12 @@ final class SetUserTimezoneSubscriber implements EventSubscriberInterface //Check if the user has set a timezone $user = $this->security->getUser(); - if ($user instanceof User && !empty($user->getTimezone())) { + if ($user instanceof User && ($user->getTimezone() !== null && $user->getTimezone() !== '')) { $timezone = $user->getTimezone(); } //Fill with default value if needed - if (null === $timezone && !empty($this->default_timezone)) { + if (null === $timezone && $this->default_timezone !== '') { $timezone = $this->default_timezone; } diff --git a/src/EventSubscriber/UserSystem/UpgradePermissionsSchemaSubscriber.php b/src/EventSubscriber/UserSystem/UpgradePermissionsSchemaSubscriber.php index 1d03cb83..613bc6ec 100644 --- a/src/EventSubscriber/UserSystem/UpgradePermissionsSchemaSubscriber.php +++ b/src/EventSubscriber/UserSystem/UpgradePermissionsSchemaSubscriber.php @@ -1,4 +1,7 @@ . */ - namespace App\EventSubscriber\UserSystem; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\User\UserInterface; use App\Entity\UserSystem\User; use App\Services\LogSystem\EventCommentHelper; use App\Services\UserSystem\PermissionSchemaUpdater; @@ -28,24 +32,14 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpKernel\Event\RequestEvent; use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\Security\Core\Security; /** * The purpose of this event subscriber is to check if the permission schema of the current user is up-to-date and upgrade it automatically if needed. */ class UpgradePermissionsSchemaSubscriber implements EventSubscriberInterface { - private Security $security; - private PermissionSchemaUpdater $permissionSchemaUpdater; - private EntityManagerInterface $entityManager; - private EventCommentHelper $eventCommentHelper; - - public function __construct(Security $security, PermissionSchemaUpdater $permissionSchemaUpdater, EntityManagerInterface $entityManager, EventCommentHelper $eventCommentHelper) + public function __construct(private readonly Security $security, private readonly PermissionSchemaUpdater $permissionSchemaUpdater, private readonly EntityManagerInterface $entityManager, private readonly EventCommentHelper $eventCommentHelper) { - $this->security = $security; - $this->permissionSchemaUpdater = $permissionSchemaUpdater; - $this->entityManager = $entityManager; - $this->eventCommentHelper = $eventCommentHelper; } public function onRequest(RequestEvent $event): void @@ -55,7 +49,7 @@ class UpgradePermissionsSchemaSubscriber implements EventSubscriberInterface } $user = $this->security->getUser(); - if (null === $user) { + if (!$user instanceof UserInterface) { //Retrieve anonymous user $user = $this->entityManager->getRepository(User::class)->getAnonymousUser(); } @@ -64,6 +58,11 @@ class UpgradePermissionsSchemaSubscriber implements EventSubscriberInterface $session = $event->getRequest()->getSession(); $flashBag = $session->getFlashBag(); + //Check if the user is an instance of User, otherwise we can't upgrade the schema + if (!$user instanceof User) { + return; + } + if ($this->permissionSchemaUpdater->isSchemaUpdateNeeded($user)) { $this->eventCommentHelper->setMessage('Automatic permission schema update'); $this->permissionSchemaUpdater->userUpgradeSchemaRecursively($user); @@ -76,4 +75,4 @@ class UpgradePermissionsSchemaSubscriber implements EventSubscriberInterface { return [KernelEvents::REQUEST => 'onRequest']; } -} \ No newline at end of file +} diff --git a/src/Events/SecurityEvent.php b/src/Events/SecurityEvent.php index 68f277c5..883460a7 100644 --- a/src/Events/SecurityEvent.php +++ b/src/Events/SecurityEvent.php @@ -50,11 +50,8 @@ use Symfony\Contracts\EventDispatcher\Event; */ class SecurityEvent extends Event { - protected User $targetUser; - - public function __construct(User $targetUser) + public function __construct(protected User $targetUser) { - $this->targetUser = $targetUser; } /** diff --git a/src/Events/SecurityEvents.php b/src/Events/SecurityEvents.php index 6ac1f882..c7c43882 100644 --- a/src/Events/SecurityEvents.php +++ b/src/Events/SecurityEvents.php @@ -43,13 +43,13 @@ namespace App\Events; class SecurityEvents { - public const PASSWORD_CHANGED = 'security.password_changed'; - public const PASSWORD_RESET = 'security.password_reset'; - public const BACKUP_KEYS_RESET = 'security.backup_keys_reset'; - public const U2F_ADDED = 'security.u2f_added'; - public const U2F_REMOVED = 'security.u2f_removed'; - public const GOOGLE_ENABLED = 'security.google_enabled'; - public const GOOGLE_DISABLED = 'security.google_disabled'; - public const TRUSTED_DEVICE_RESET = 'security.trusted_device_reset'; - public const TFA_ADMIN_RESET = 'security.2fa_admin_reset'; + final public const PASSWORD_CHANGED = 'security.password_changed'; + final public const PASSWORD_RESET = 'security.password_reset'; + final public const BACKUP_KEYS_RESET = 'security.backup_keys_reset'; + final public const U2F_ADDED = 'security.u2f_added'; + final public const U2F_REMOVED = 'security.u2f_removed'; + final public const GOOGLE_ENABLED = 'security.google_enabled'; + final public const GOOGLE_DISABLED = 'security.google_disabled'; + final public const TRUSTED_DEVICE_RESET = 'security.trusted_device_reset'; + final public const TFA_ADMIN_RESET = 'security.2fa_admin_reset'; } diff --git a/src/Exceptions/InvalidRegexException.php b/src/Exceptions/InvalidRegexException.php index a94201d0..aaa2a897 100644 --- a/src/Exceptions/InvalidRegexException.php +++ b/src/Exceptions/InvalidRegexException.php @@ -1,4 +1,7 @@ . */ - namespace App\Exceptions; use Doctrine\DBAL\Exception\DriverException; @@ -25,17 +27,13 @@ use ErrorException; class InvalidRegexException extends \RuntimeException { - private ?string $reason; - - public function __construct(string $reason = null) + public function __construct(private readonly ?string $reason = null) { - $this->reason = $reason; parent::__construct('Invalid regular expression'); } /** * Returns the reason for the exception (what the regex driver deemed invalid) - * @return string|null */ public function getReason(): ?string { @@ -44,8 +42,6 @@ class InvalidRegexException extends \RuntimeException /** * Creates a new exception from a driver exception happening, when MySQL encounters an invalid regex - * @param DriverException $exception - * @return self */ public static function fromDriverException(DriverException $exception): self { @@ -62,13 +58,11 @@ class InvalidRegexException extends \RuntimeException /** * Creates a new exception from the errorException thrown by mb_ereg - * @param ErrorException $ex - * @return self */ public static function fromMBRegexError(ErrorException $ex): self { //Ensure that the error is really a mb_ereg error - if ($ex->getSeverity() !== E_WARNING || !strpos($ex->getMessage(), 'mb_ereg()') !== false) { + if ($ex->getSeverity() !== E_WARNING || !strpos($ex->getMessage(), 'mb_ereg()')) { throw new \InvalidArgumentException('The given exception is not a mb_ereg error', 0, $ex); } @@ -77,4 +71,4 @@ class InvalidRegexException extends \RuntimeException return new self($reason); } -} \ No newline at end of file +} diff --git a/src/Form/AdminPages/AttachmentTypeAdminForm.php b/src/Form/AdminPages/AttachmentTypeAdminForm.php index 57ba6fed..d777d4d4 100644 --- a/src/Form/AdminPages/AttachmentTypeAdminForm.php +++ b/src/Form/AdminPages/AttachmentTypeAdminForm.php @@ -22,21 +22,18 @@ declare(strict_types=1); namespace App\Form\AdminPages; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractNamedDBElement; use App\Services\Attachments\FileTypeFilterTools; use App\Services\LogSystem\EventCommentNeededHelper; use Symfony\Component\Form\CallbackTransformer; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Security\Core\Security; class AttachmentTypeAdminForm extends BaseEntityAdminForm { - protected FileTypeFilterTools $filterTools; - - public function __construct(Security $security, FileTypeFilterTools $filterTools, EventCommentNeededHelper $eventCommentNeededHelper) + public function __construct(Security $security, protected FileTypeFilterTools $filterTools, EventCommentNeededHelper $eventCommentNeededHelper) { - $this->filterTools = $filterTools; parent::__construct($security, $eventCommentNeededHelper); } @@ -58,12 +55,8 @@ class AttachmentTypeAdminForm extends BaseEntityAdminForm //Normalize data before writing it to database $builder->get('filetype_filter')->addViewTransformer(new CallbackTransformer( - static function ($value) { - return $value; - }, - function ($value) { - return $this->filterTools->normalizeFilterString($value); - } + static fn($value) => $value, + fn($value) => $this->filterTools->normalizeFilterString($value) )); } } diff --git a/src/Form/AdminPages/BaseEntityAdminForm.php b/src/Form/AdminPages/BaseEntityAdminForm.php index 31dd39f0..268ea630 100644 --- a/src/Form/AdminPages/BaseEntityAdminForm.php +++ b/src/Form/AdminPages/BaseEntityAdminForm.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Form\AdminPages; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\LabelSystem\LabelProfile; @@ -40,17 +41,11 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Core\Security; class BaseEntityAdminForm extends AbstractType { - protected Security $security; - protected EventCommentNeededHelper $eventCommentNeededHelper; - - public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper) + public function __construct(protected Security $security, protected EventCommentNeededHelper $eventCommentNeededHelper) { - $this->security = $security; - $this->eventCommentNeededHelper = $eventCommentNeededHelper; } public function configureOptions(OptionsResolver $resolver): void @@ -82,7 +77,7 @@ class BaseEntityAdminForm extends AbstractType 'parent', StructuralEntityType::class, [ - 'class' => get_class($entity), + 'class' => $entity::class, 'required' => false, 'label' => 'parent.label', 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), diff --git a/src/Form/AdminPages/CurrencyAdminForm.php b/src/Form/AdminPages/CurrencyAdminForm.php index 754d7c66..a3514b80 100644 --- a/src/Form/AdminPages/CurrencyAdminForm.php +++ b/src/Form/AdminPages/CurrencyAdminForm.php @@ -22,22 +22,19 @@ declare(strict_types=1); namespace App\Form\AdminPages; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractNamedDBElement; use App\Form\Type\BigDecimalMoneyType; use App\Services\LogSystem\EventCommentNeededHelper; use Symfony\Component\Form\Extension\Core\Type\CurrencyType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Security\Core\Security; class CurrencyAdminForm extends BaseEntityAdminForm { - private string $default_currency; - - public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, string $default_currency) + public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, private readonly string $base_currency) { parent::__construct($security, $eventCommentNeededHelper); - $this->default_currency = $default_currency; } protected function additionalFormElements(FormBuilderInterface $builder, array $options, AbstractNamedDBElement $entity): void @@ -54,7 +51,7 @@ class CurrencyAdminForm extends BaseEntityAdminForm $builder->add('exchange_rate', BigDecimalMoneyType::class, [ 'required' => false, 'label' => 'currency.edit.exchange_rate', - 'currency' => $this->default_currency, + 'currency' => $this->base_currency, 'scale' => 6, 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), ]); diff --git a/src/Form/AdminPages/ImportType.php b/src/Form/AdminPages/ImportType.php index ded1da2f..3e87812c 100644 --- a/src/Form/AdminPages/ImportType.php +++ b/src/Form/AdminPages/ImportType.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Form\AdminPages; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Parts\Category; use App\Entity\Parts\Part; @@ -33,15 +34,11 @@ use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Security\Core\Security; class ImportType extends AbstractType { - protected Security $security; - - public function __construct(Security $security) + public function __construct(protected Security $security) { - $this->security = $security; } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Form/AdminPages/MassCreationForm.php b/src/Form/AdminPages/MassCreationForm.php index 27ee8287..4948cdd5 100644 --- a/src/Form/AdminPages/MassCreationForm.php +++ b/src/Form/AdminPages/MassCreationForm.php @@ -22,21 +22,18 @@ declare(strict_types=1); namespace App\Form\AdminPages; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractStructuralDBElement; use App\Form\Type\StructuralEntityType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Security\Core\Security; class MassCreationForm extends AbstractType { - protected Security $security; - - public function __construct(Security $security) + public function __construct(protected Security $security) { - $this->security = $security; } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Form/AdminPages/ProjectAdminForm.php b/src/Form/AdminPages/ProjectAdminForm.php index ebe01599..2d4683c9 100644 --- a/src/Form/AdminPages/ProjectAdminForm.php +++ b/src/Form/AdminPages/ProjectAdminForm.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\AdminPages; use App\Entity\Base\AbstractNamedDBElement; @@ -59,4 +61,4 @@ class ProjectAdminForm extends BaseEntityAdminForm ], ]); } -} \ No newline at end of file +} diff --git a/src/Form/AdminPages/SupplierForm.php b/src/Form/AdminPages/SupplierForm.php index db798db8..34b3b27a 100644 --- a/src/Form/AdminPages/SupplierForm.php +++ b/src/Form/AdminPages/SupplierForm.php @@ -22,22 +22,19 @@ declare(strict_types=1); namespace App\Form\AdminPages; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\PriceInformations\Currency; use App\Form\Type\BigDecimalMoneyType; use App\Form\Type\StructuralEntityType; use App\Services\LogSystem\EventCommentNeededHelper; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\Security\Core\Security; class SupplierForm extends CompanyForm { - protected string $default_currency; - - public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, string $default_currency) + public function __construct(Security $security, EventCommentNeededHelper $eventCommentNeededHelper, protected string $base_currency) { parent::__construct($security, $eventCommentNeededHelper); - $this->default_currency = $default_currency; } protected function additionalFormElements(FormBuilderInterface $builder, array $options, AbstractNamedDBElement $entity): void @@ -56,7 +53,7 @@ class SupplierForm extends CompanyForm $builder->add('shipping_costs', BigDecimalMoneyType::class, [ 'required' => false, - 'currency' => $this->default_currency, + 'currency' => $this->base_currency, 'scale' => 3, 'label' => 'supplier.shipping_costs.label', 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), diff --git a/src/Form/AttachmentFormType.php b/src/Form/AttachmentFormType.php index 454f34b0..71c0bedd 100644 --- a/src/Form/AttachmentFormType.php +++ b/src/Form/AttachmentFormType.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Form; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentType; use App\Form\Type\StructuralEntityType; @@ -41,32 +42,14 @@ use Symfony\Component\Form\FormView; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Validator\Constraints\File; use Symfony\Component\Validator\Constraints\Url; use Symfony\Contracts\Translation\TranslatorInterface; class AttachmentFormType extends AbstractType { - protected AttachmentManager $attachment_helper; - protected UrlGeneratorInterface $urlGenerator; - protected bool $allow_attachments_download; - protected string $max_file_size; - protected Security $security; - protected AttachmentSubmitHandler $submitHandler; - protected TranslatorInterface $translator; - - public function __construct(AttachmentManager $attachmentHelper, UrlGeneratorInterface $urlGenerator, - Security $security, AttachmentSubmitHandler $submitHandler, TranslatorInterface $translator, - bool $allow_attachments_downloads, string $max_file_size) + public function __construct(protected AttachmentManager $attachment_helper, protected UrlGeneratorInterface $urlGenerator, protected Security $security, protected AttachmentSubmitHandler $submitHandler, protected TranslatorInterface $translator, protected bool $allow_attachments_download, protected string $max_file_size) { - $this->attachment_helper = $attachmentHelper; - $this->urlGenerator = $urlGenerator; - $this->allow_attachments_download = $allow_attachments_downloads; - $this->security = $security; - $this->submitHandler = $submitHandler; - $this->translator = $translator; - $this->max_file_size = $max_file_size; } public function buildForm(FormBuilderInterface $builder, array $options): void @@ -161,7 +144,7 @@ class AttachmentFormType extends AbstractType } //If the name is empty, use the original file name as attachment name - if (empty($attachment->getName())) { + if ($attachment->getName() === '') { $attachment->setName($file->getClientOriginalName()); } }, 100000); @@ -187,7 +170,7 @@ class AttachmentFormType extends AbstractType ]); } - public function finishView(FormView $view, FormInterface $form, array $options) + public function finishView(FormView $view, FormInterface $form, array $options): void { $view->vars['max_upload_size'] = $this->submitHandler->getMaximumAllowedUploadSize(); } diff --git a/src/Form/CollectionTypeExtension.php b/src/Form/CollectionTypeExtension.php index 1c0c8d63..4fa93852 100644 --- a/src/Form/CollectionTypeExtension.php +++ b/src/Form/CollectionTypeExtension.php @@ -67,11 +67,8 @@ use Symfony\Component\PropertyAccess\PropertyAccessorInterface; */ class CollectionTypeExtension extends AbstractTypeExtension { - protected PropertyAccessorInterface $propertyAccess; - - public function __construct(PropertyAccessorInterface $propertyAccess) + public function __construct(protected PropertyAccessorInterface $propertyAccess) { - $this->propertyAccess = $propertyAccess; } public static function getExtendedTypes(): iterable @@ -93,9 +90,7 @@ class CollectionTypeExtension extends AbstractTypeExtension //Set a unique prototype name, so that we can use nested collections $resolver->setDefaults([ - 'prototype_name' => function (Options $options) { - return '__name_'.uniqid("", false) . '__'; - }, + 'prototype_name' => fn(Options $options): string => '__name_'.uniqid("", false) . '__', ]); $resolver->setAllowedTypes('reindex_enable', 'bool'); @@ -103,7 +98,7 @@ class CollectionTypeExtension extends AbstractTypeExtension $resolver->setAllowedTypes('reindex_path', 'string'); } - public function finishView(FormView $view, FormInterface $form, array $options) + public function finishView(FormView $view, FormInterface $form, array $options): void { parent::finishView($view, $form, $options); //Add prototype name to view, so that we can pass it to the stimulus controller @@ -156,7 +151,7 @@ class CollectionTypeExtension extends AbstractTypeExtension //The validator uses the number of the element as index, so we have to map the errors to the correct index $error_mapping = []; $n = 0; - foreach ($data as $key => $item) { + foreach (array_keys($data) as $key) { $error_mapping['['.$n.']'] = $key; $n++; } @@ -167,9 +162,11 @@ class CollectionTypeExtension extends AbstractTypeExtension /** * Set the option of the form. * This a bit hacky because we access private properties.... - * + * @param FormConfigInterface $builder The form on which the option should be set + * @param string $option The option which should be changed + * @param mixed $value The new value */ - public function setOption(FormConfigInterface $builder, string $option, $value): void + public function setOption(FormConfigInterface $builder, string $option, mixed $value): void { if (!$builder instanceof FormConfigBuilder) { throw new \RuntimeException('This method only works with FormConfigBuilder instances.'); @@ -178,10 +175,8 @@ class CollectionTypeExtension extends AbstractTypeExtension //We have to use FormConfigBuilder::class here, because options is private and not available in subclasses $reflection = new ReflectionClass(FormConfigBuilder::class); $property = $reflection->getProperty('options'); - $property->setAccessible(true); $tmp = $property->getValue($builder); $tmp[$option] = $value; $property->setValue($builder, $tmp); - $property->setAccessible(false); } } diff --git a/src/Form/Filters/AttachmentFilterType.php b/src/Form/Filters/AttachmentFilterType.php index 1b4967f5..92cb20b6 100644 --- a/src/Form/Filters/AttachmentFilterType.php +++ b/src/Form/Filters/AttachmentFilterType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters; use App\DataTables\Filters\AttachmentFilter; @@ -58,7 +60,7 @@ class AttachmentFilterType extends AbstractType ]); } - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('dbId', NumberConstraintType::class, [ 'label' => 'part.filter.dbId', @@ -114,4 +116,4 @@ class AttachmentFilterType extends AbstractType 'label' => 'filter.discard', ]); } -} \ No newline at end of file +} diff --git a/src/Form/Filters/Constraints/BooleanConstraintType.php b/src/Form/Filters/Constraints/BooleanConstraintType.php index ebc5ce09..6669b5a7 100644 --- a/src/Form/Filters/Constraints/BooleanConstraintType.php +++ b/src/Form/Filters/Constraints/BooleanConstraintType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters\Constraints; use App\DataTables\Filters\Constraints\BooleanConstraint; @@ -46,9 +48,9 @@ class BooleanConstraintType extends AbstractType ]); } - public function finishView(FormView $view, FormInterface $form, array $options) + public function finishView(FormView $view, FormInterface $form, array $options): void { //Remove the label from the compound form, as the checkbox already has a label $view->vars['label'] = false; } -} \ No newline at end of file +} diff --git a/src/Form/Filters/Constraints/ChoiceConstraintType.php b/src/Form/Filters/Constraints/ChoiceConstraintType.php index 16014c7f..70d37b08 100644 --- a/src/Form/Filters/Constraints/ChoiceConstraintType.php +++ b/src/Form/Filters/Constraints/ChoiceConstraintType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters\Constraints; use App\DataTables\Filters\Constraints\ChoiceConstraint; @@ -63,4 +65,4 @@ class ChoiceConstraintType extends AbstractType ]); } -} \ No newline at end of file +} diff --git a/src/Form/Filters/Constraints/DateTimeConstraintType.php b/src/Form/Filters/Constraints/DateTimeConstraintType.php index b24346ee..ffd3aafd 100644 --- a/src/Form/Filters/Constraints/DateTimeConstraintType.php +++ b/src/Form/Filters/Constraints/DateTimeConstraintType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters\Constraints; use App\DataTables\Filters\Constraints\DateTimeConstraint; @@ -84,10 +86,10 @@ class DateTimeConstraintType extends AbstractType ]); } - public function buildView(FormView $view, FormInterface $form, array $options) + public function buildView(FormView $view, FormInterface $form, array $options): void { parent::buildView($view, $form, $options); $view->vars['text_suffix'] = $options['text_suffix']; } -} \ No newline at end of file +} diff --git a/src/Form/Filters/Constraints/EnumConstraintType.php b/src/Form/Filters/Constraints/EnumConstraintType.php new file mode 100644 index 00000000..59259169 --- /dev/null +++ b/src/Form/Filters/Constraints/EnumConstraintType.php @@ -0,0 +1,73 @@ +. + */ +namespace App\Form\Filters\Constraints; + +use App\DataTables\Filters\Constraints\ChoiceConstraint; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; + +class EnumConstraintType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setRequired('enum_class'); + $resolver->setAllowedTypes('enum_class', 'string'); + + $resolver->setRequired('choice_label'); + $resolver->setAllowedTypes('choice_label', ['string', 'callable']); + + $resolver->setDefaults([ + 'compound' => true, + 'data_class' => ChoiceConstraint::class, + ]); + + } + + public function buildForm(FormBuilderInterface $builder, array $options): void + { + $choices = [ + '' => '', + 'filter.choice_constraint.operator.ANY' => 'ANY', + 'filter.choice_constraint.operator.NONE' => 'NONE', + ]; + + $builder->add('operator', ChoiceType::class, [ + 'choices' => $choices, + 'required' => false, + ]); + + $builder->add('value', EnumType::class, [ + 'class' => $options['enum_class'], + 'choice_label' => $options['choice_label'], + 'required' => false, + 'multiple' => true, + 'attr' => [ + 'data-controller' => 'elements--select-multiple', + ] + ]); + } + +} diff --git a/src/Form/Filters/Constraints/InstanceOfConstraintType.php b/src/Form/Filters/Constraints/InstanceOfConstraintType.php index 4a776cd6..02de15e5 100644 --- a/src/Form/Filters/Constraints/InstanceOfConstraintType.php +++ b/src/Form/Filters/Constraints/InstanceOfConstraintType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters\Constraints; use App\DataTables\Filters\Constraints\InstanceOfConstraint; @@ -27,14 +29,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class InstanceOfConstraintType extends AbstractType { - protected EntityManagerInterface $em; - - public function __construct(EntityManagerInterface $entityManager) + public function __construct(protected EntityManagerInterface $em) { - $this->em = $entityManager; } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefault('data_class', InstanceOfConstraint::class); } @@ -43,4 +42,4 @@ class InstanceOfConstraintType extends AbstractType { return ChoiceConstraintType::class; } -} \ No newline at end of file +} diff --git a/src/Form/Filters/Constraints/NumberConstraintType.php b/src/Form/Filters/Constraints/NumberConstraintType.php index a04fc9a1..ad792fa0 100644 --- a/src/Form/Filters/Constraints/NumberConstraintType.php +++ b/src/Form/Filters/Constraints/NumberConstraintType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters\Constraints; use App\DataTables\Filters\Constraints\NumberConstraint; @@ -91,10 +93,8 @@ class NumberConstraintType extends AbstractType ]); } - public function buildView(FormView $view, FormInterface $form, array $options) + public function buildView(FormView $view, FormInterface $form, array $options): void { - parent::buildView($view, $form, $options); - $view->vars['text_suffix'] = $options['text_suffix']; } -} \ No newline at end of file +} diff --git a/src/Form/Filters/Constraints/ParameterConstraintType.php b/src/Form/Filters/Constraints/ParameterConstraintType.php index 10c58429..52fbc726 100644 --- a/src/Form/Filters/Constraints/ParameterConstraintType.php +++ b/src/Form/Filters/Constraints/ParameterConstraintType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters\Constraints; use App\DataTables\Filters\Constraints\Part\ParameterConstraint; @@ -75,4 +77,4 @@ class ParameterConstraintType extends AbstractType } }); } -} \ No newline at end of file +} diff --git a/src/Form/Filters/Constraints/ParameterValueConstraintType.php b/src/Form/Filters/Constraints/ParameterValueConstraintType.php index a99fd2a4..01fbca5c 100644 --- a/src/Form/Filters/Constraints/ParameterValueConstraintType.php +++ b/src/Form/Filters/Constraints/ParameterValueConstraintType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters\Constraints; class ParameterValueConstraintType extends NumberConstraintType @@ -48,4 +50,4 @@ class ParameterValueConstraintType extends NumberConstraintType { return NumberConstraintType::class; } -} \ No newline at end of file +} diff --git a/src/Form/Filters/Constraints/StructuralEntityConstraintType.php b/src/Form/Filters/Constraints/StructuralEntityConstraintType.php index 1ef6a333..5191881b 100644 --- a/src/Form/Filters/Constraints/StructuralEntityConstraintType.php +++ b/src/Form/Filters/Constraints/StructuralEntityConstraintType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters\Constraints; use App\DataTables\Filters\Constraints\EntityConstraint; @@ -65,9 +67,9 @@ class StructuralEntityConstraintType extends AbstractType ]); } - public function buildView(FormView $view, FormInterface $form, array $options) + public function buildView(FormView $view, FormInterface $form, array $options): void { parent::buildView($view, $form, $options); $view->vars['text_suffix'] = $options['text_suffix']; } -} \ No newline at end of file +} diff --git a/src/Form/Filters/Constraints/TagsConstraintType.php b/src/Form/Filters/Constraints/TagsConstraintType.php index e6134a65..0a7661dd 100644 --- a/src/Form/Filters/Constraints/TagsConstraintType.php +++ b/src/Form/Filters/Constraints/TagsConstraintType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters\Constraints; use App\DataTables\Filters\Constraints\Part\TagsConstraint; @@ -30,11 +32,8 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; class TagsConstraintType extends AbstractType { - protected UrlGeneratorInterface $urlGenerator; - - public function __construct(UrlGeneratorInterface $urlGenerator) + public function __construct(protected UrlGeneratorInterface $urlGenerator) { - $this->urlGenerator = $urlGenerator; } public function configureOptions(OptionsResolver $resolver): void @@ -71,4 +70,4 @@ class TagsConstraintType extends AbstractType 'required' => false, ]); } -} \ No newline at end of file +} diff --git a/src/Form/Filters/Constraints/TextConstraintType.php b/src/Form/Filters/Constraints/TextConstraintType.php index b9415977..492278d2 100644 --- a/src/Form/Filters/Constraints/TextConstraintType.php +++ b/src/Form/Filters/Constraints/TextConstraintType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters\Constraints; use App\DataTables\Filters\Constraints\TextConstraint; @@ -70,10 +72,10 @@ class TextConstraintType extends AbstractType ]); } - public function buildView(FormView $view, FormInterface $form, array $options) + public function buildView(FormView $view, FormInterface $form, array $options): void { parent::buildView($view, $form, $options); $view->vars['text_suffix'] = $options['text_suffix']; } -} \ No newline at end of file +} diff --git a/src/Form/Filters/Constraints/UserEntityConstraintType.php b/src/Form/Filters/Constraints/UserEntityConstraintType.php index 86c9eb97..8c82e0d8 100644 --- a/src/Form/Filters/Constraints/UserEntityConstraintType.php +++ b/src/Form/Filters/Constraints/UserEntityConstraintType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters\Constraints; use App\DataTables\Filters\Constraints\EntityConstraint; @@ -60,9 +62,9 @@ class UserEntityConstraintType extends AbstractType ]); } - public function buildView(FormView $view, FormInterface $form, array $options) + public function buildView(FormView $view, FormInterface $form, array $options): void { parent::buildView($view, $form, $options); $view->vars['text_suffix'] = $options['text_suffix']; } -} \ No newline at end of file +} diff --git a/src/Form/Filters/LogFilterType.php b/src/Form/Filters/LogFilterType.php index ad1082b7..0d8257f4 100644 --- a/src/Form/Filters/LogFilterType.php +++ b/src/Form/Filters/LogFilterType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters; use App\DataTables\Filters\LogFilter; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentType; +use App\Entity\LogSystem\LogLevel; +use App\Entity\LogSystem\LogTargetType; use App\Entity\LogSystem\PartStockChangedLogEntry; use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; @@ -54,6 +58,7 @@ use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use App\Form\Filters\Constraints\ChoiceConstraintType; use App\Form\Filters\Constraints\DateTimeConstraintType; +use App\Form\Filters\Constraints\EnumConstraintType; use App\Form\Filters\Constraints\InstanceOfConstraintType; use App\Form\Filters\Constraints\NumberConstraintType; use App\Form\Filters\Constraints\UserEntityConstraintType; @@ -65,17 +70,6 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class LogFilterType extends AbstractType { - protected const LEVEL_CHOICES = [ - 'log.level.debug' => AbstractLogEntry::LEVEL_DEBUG, - 'log.level.info' => AbstractLogEntry::LEVEL_INFO, - 'log.level.notice' => AbstractLogEntry::LEVEL_NOTICE, - 'log.level.warning' => AbstractLogEntry::LEVEL_WARNING, - 'log.level.error' => AbstractLogEntry::LEVEL_ERROR, - 'log.level.critical' => AbstractLogEntry::LEVEL_CRITICAL, - 'log.level.alert' => AbstractLogEntry::LEVEL_ALERT, - 'log.level.emergency' => AbstractLogEntry::LEVEL_EMERGENCY, - ]; - protected const TARGET_TYPE_CHOICES = [ 'log.type.collection_element_deleted' => CollectionElementDeleted::class, 'log.type.database_updated' => DatabaseUpdatedLogEntry::class, @@ -101,7 +95,7 @@ class LogFilterType extends AbstractType ]); } - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('dbId', NumberConstraintType::class, [ 'label' => 'part.filter.dbId', @@ -115,9 +109,10 @@ class LogFilterType extends AbstractType - $builder->add('level', ChoiceConstraintType::class, [ + $builder->add('level', EnumConstraintType::class, [ 'label' => 'log.level', - 'choices' => self::LEVEL_CHOICES, + 'enum_class' => LogLevel::class, + 'choice_label' => fn(LogLevel $level): string => 'log.level.' . $level->toPSR3LevelString(), ]); $builder->add('eventType', InstanceOfConstraintType::class, [ @@ -129,29 +124,31 @@ class LogFilterType extends AbstractType 'label' => 'log.user', ]); - $builder->add('targetType', ChoiceConstraintType::class, [ + $builder->add('targetType', EnumConstraintType::class, [ 'label' => 'log.target_type', - 'choices' => [ - 'user.label' => AbstractLogEntry::targetTypeClassToID(User::class), - 'attachment.label' => AbstractLogEntry::targetTypeClassToID(Attachment::class), - 'attachment_type.label' => AbstractLogEntry::targetTypeClassToID(AttachmentType::class), - 'category.label' => AbstractLogEntry::targetTypeClassToID(Category::class), - 'project.label' => AbstractLogEntry::targetTypeClassToID(Project::class), - 'project_bom_entry.label' => AbstractLogEntry::targetTypeClassToID(ProjectBOMEntry::class), - 'footprint.label' => AbstractLogEntry::targetTypeClassToID(Footprint::class), - 'group.label' => AbstractLogEntry::targetTypeClassToID(Group::class), - 'manufacturer.label' => AbstractLogEntry::targetTypeClassToID(Manufacturer::class), - 'part.label' => AbstractLogEntry::targetTypeClassToID(Part::class), - 'storelocation.label' => AbstractLogEntry::targetTypeClassToID(Storelocation::class), - 'supplier.label' => AbstractLogEntry::targetTypeClassToID(Supplier::class), - 'part_lot.label' => AbstractLogEntry::targetTypeClassToID(PartLot::class), - 'currency.label' => AbstractLogEntry::targetTypeClassToID(Currency::class), - 'orderdetail.label' => AbstractLogEntry::targetTypeClassToID(Orderdetail::class), - 'pricedetail.label' => AbstractLogEntry::targetTypeClassToID(Pricedetail::class), - 'measurement_unit.label' => AbstractLogEntry::targetTypeClassToID(MeasurementUnit::class), - 'parameter.label' => AbstractLogEntry::targetTypeClassToID(AbstractParameter::class), - 'label_profile.label' => AbstractLogEntry::targetTypeClassToID(LabelProfile::class), - ] + 'enum_class' => LogTargetType::class, + 'choice_label' => fn(LogTargetType $type): string => match ($type) { + LogTargetType::NONE => 'log.target_type.none', + LogTargetType::USER => 'user.label', + LogTargetType::ATTACHMENT => 'attachment.label', + LogTargetType::ATTACHMENT_TYPE => 'attachment_type.label', + LogTargetType::CATEGORY => 'category.label', + LogTargetType::PROJECT => 'project.label', + LogTargetType::BOM_ENTRY => 'project_bom_entry.label', + LogTargetType::FOOTPRINT => 'footprint.label', + LogTargetType::GROUP => 'group.label', + LogTargetType::MANUFACTURER => 'manufacturer.label', + LogTargetType::PART => 'part.label', + LogTargetType::STORELOCATION => 'storelocation.label', + LogTargetType::SUPPLIER => 'supplier.label', + LogTargetType::PART_LOT => 'part_lot.label', + LogTargetType::CURRENCY => 'currency.label', + LogTargetType::ORDERDETAIL => 'orderdetail.label', + LogTargetType::PRICEDETAIL => 'pricedetail.label', + LogTargetType::MEASUREMENT_UNIT => 'measurement_unit.label', + LogTargetType::PARAMETER => 'parameter.label', + LogTargetType::LABEL_PROFILE => 'label_profile.label', + }, ]); $builder->add('targetId', NumberConstraintType::class, [ @@ -168,4 +165,4 @@ class LogFilterType extends AbstractType 'label' => 'filter.discard', ]); } -} \ No newline at end of file +} diff --git a/src/Form/Filters/PartFilterType.php b/src/Form/Filters/PartFilterType.php index 249b0c1c..5533189c 100644 --- a/src/Form/Filters/PartFilterType.php +++ b/src/Form/Filters/PartFilterType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Filters; use App\DataTables\Filters\Constraints\Part\ParameterConstraint; @@ -59,7 +61,7 @@ class PartFilterType extends AbstractType ]); } - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { /* * Common tab @@ -277,4 +279,4 @@ class PartFilterType extends AbstractType ]); } -} \ No newline at end of file +} diff --git a/src/Form/LabelOptionsType.php b/src/Form/LabelOptionsType.php index 5af69ce6..0b15046c 100644 --- a/src/Form/LabelOptionsType.php +++ b/src/Form/LabelOptionsType.php @@ -41,23 +41,24 @@ declare(strict_types=1); namespace App\Form; +use App\Entity\LabelSystem\BarcodeType; +use App\Entity\LabelSystem\LabelProcessMode; +use App\Entity\LabelSystem\LabelSupportedElement; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\LabelSystem\LabelOptions; use App\Form\Type\RichTextEditorType; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Core\Security; class LabelOptionsType extends AbstractType { - private Security $security; - - public function __construct(Security $security) + public function __construct(private readonly Security $security) { - $this->security = $security; } public function buildForm(FormBuilderInterface $builder, array $options): void @@ -81,31 +82,33 @@ class LabelOptionsType extends AbstractType ], ]); - $builder->add('supported_element', ChoiceType::class, [ + $builder->add('supported_element', EnumType::class, [ 'label' => 'label_options.supported_elements.label', - 'choices' => [ - 'part.label' => 'part', - 'part_lot.label' => 'part_lot', - 'storelocation.label' => 'storelocation', - ], + 'class' => LabelSupportedElement::class, + 'choice_label' => fn(LabelSupportedElement $choice) => match($choice) { + LabelSupportedElement::PART => 'part.label', + LabelSupportedElement::PART_LOT => 'part_lot.label', + LabelSupportedElement::STORELOCATION => 'storelocation.label', + }, ]); - $builder->add('barcode_type', ChoiceType::class, [ + $builder->add('barcode_type', EnumType::class, [ 'label' => 'label_options.barcode_type.label', 'empty_data' => 'none', - 'choices' => [ - 'label_options.barcode_type.none' => 'none', - 'label_options.barcode_type.qr' => 'qr', - 'label_options.barcode_type.code128' => 'code128', - 'label_options.barcode_type.code39' => 'code39', - 'label_options.barcode_type.code93' => 'code93', - 'label_options.barcode_type.datamatrix' => 'datamatrix', - ], - 'group_by' => static function ($choice, $key, $value) { - if (in_array($choice, ['qr', 'datamatrix'], true)) { + 'class' => BarcodeType::class, + 'choice_label' => fn(BarcodeType $choice) => match($choice) { + BarcodeType::NONE => 'label_options.barcode_type.none', + BarcodeType::QR => 'label_options.barcode_type.qr', + BarcodeType::CODE128 => 'label_options.barcode_type.code128', + BarcodeType::CODE39 => 'label_options.barcode_type.code39', + BarcodeType::CODE93 => 'label_options.barcode_type.code93', + BarcodeType::DATAMATRIX => 'label_options.barcode_type.datamatrix', + }, + 'group_by' => static function (BarcodeType $choice, $key, $value): ?string { + if ($choice->is2D()) { return 'label_options.barcode_type.2D'; } - if (in_array($choice, ['code39', 'code93', 'code128'], true)) { + if ($choice->is1D()) { return 'label_options.barcode_type.1D'; } @@ -132,12 +135,13 @@ class LabelOptionsType extends AbstractType 'required' => false, ]); - $builder->add('lines_mode', ChoiceType::class, [ + $builder->add('process_mode', EnumType::class, [ 'label' => 'label_options.lines_mode.label', - 'choices' => [ - 'label_options.lines_mode.html' => 'html', - 'label.options.lines_mode.twig' => 'twig', - ], + 'class' => LabelProcessMode::class, + 'choice_label' => fn(LabelProcessMode $choice) => match($choice) { + LabelProcessMode::PLACEHOLDER => 'label_options.lines_mode.html', + LabelProcessMode::TWIG => 'label.options.lines_mode.twig', + }, 'help' => 'label_options.lines_mode.help', 'help_html' => true, 'expanded' => true, diff --git a/src/Form/LabelSystem/LabelDialogType.php b/src/Form/LabelSystem/LabelDialogType.php index 9bc3dfdf..33c79797 100644 --- a/src/Form/LabelSystem/LabelDialogType.php +++ b/src/Form/LabelSystem/LabelDialogType.php @@ -41,6 +41,7 @@ declare(strict_types=1); namespace App\Form\LabelSystem; +use Symfony\Bundle\SecurityBundle\Security; use App\Form\LabelOptionsType; use App\Validator\Constraints\Misc\ValidRange; use Symfony\Component\Form\AbstractType; @@ -48,15 +49,11 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Core\Security; class LabelDialogType extends AbstractType { - protected Security $security; - - public function __construct(Security $security) + public function __construct(protected Security $security) { - $this->security = $security; } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Form/ParameterType.php b/src/Form/ParameterType.php index 09293b97..66e664be 100644 --- a/src/Form/ParameterType.php +++ b/src/Form/ParameterType.php @@ -148,7 +148,7 @@ class ParameterType extends AbstractType ]); } - public function finishView(FormView $view, FormInterface $form, array $options) + public function finishView(FormView $view, FormInterface $form, array $options): void { //By default use part parameters for autocomplete $view->vars['type'] = 'part'; diff --git a/src/Form/Part/OrderdetailType.php b/src/Form/Part/OrderdetailType.php index ece78af5..53240821 100644 --- a/src/Form/Part/OrderdetailType.php +++ b/src/Form/Part/OrderdetailType.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Form\Part; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Orderdetail; @@ -36,15 +37,11 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Core\Security; class OrderdetailType extends AbstractType { - protected Security $security; - - public function __construct(Security $security) + public function __construct(protected Security $security) { - $this->security = $security; } public function buildForm(FormBuilderInterface $builder, array $options): void @@ -82,7 +79,7 @@ class OrderdetailType extends AbstractType $orderdetail = $event->getData(); $dummy_pricedetail = new Pricedetail(); - if (null !== $orderdetail && null !== $orderdetail->getSupplier()) { + if ($orderdetail instanceof Orderdetail && $orderdetail->getSupplier() instanceof Supplier) { $dummy_pricedetail->setCurrency($orderdetail->getSupplier()->getDefaultCurrency()); } diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php index 141b5eca..309197ab 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Form\Part; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Attachments\PartAttachment; use App\Entity\Parameters\PartParameter; use App\Entity\Parts\Category; @@ -48,19 +49,11 @@ use Symfony\Component\Form\Extension\Core\Type\UrlType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Security\Core\Security; class PartBaseType extends AbstractType { - protected Security $security; - protected UrlGeneratorInterface $urlGenerator; - protected EventCommentNeededHelper $event_comment_needed_helper; - - public function __construct(Security $security, UrlGeneratorInterface $urlGenerator, EventCommentNeededHelper $event_comment_needed_helper) + public function __construct(protected Security $security, protected UrlGeneratorInterface $urlGenerator, protected EventCommentNeededHelper $event_comment_needed_helper) { - $this->security = $security; - $this->urlGenerator = $urlGenerator; - $this->event_comment_needed_helper = $event_comment_needed_helper; } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Form/Part/PartLotType.php b/src/Form/Part/PartLotType.php index 2c46df1a..707caf28 100644 --- a/src/Form/Part/PartLotType.php +++ b/src/Form/Part/PartLotType.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Form\Part; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\PartLot; use App\Entity\Parts\Storelocation; @@ -34,15 +35,11 @@ use Symfony\Component\Form\Extension\Core\Type\DateType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Core\Security; class PartLotType extends AbstractType { - protected Security $security; - - public function __construct(Security $security) + public function __construct(protected Security $security) { - $this->security = $security; } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Form/Permissions/PermissionGroupType.php b/src/Form/Permissions/PermissionGroupType.php index c395337f..f3f7ffec 100644 --- a/src/Form/Permissions/PermissionGroupType.php +++ b/src/Form/Permissions/PermissionGroupType.php @@ -30,12 +30,10 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class PermissionGroupType extends AbstractType { - protected PermissionManager $resolver; protected array $perm_structure; - public function __construct(PermissionManager $resolver) + public function __construct(protected PermissionManager $resolver) { - $this->resolver = $resolver; $this->perm_structure = $resolver->getPermissionStructure(); } @@ -68,9 +66,7 @@ class PermissionGroupType extends AbstractType { parent::configureOptions($resolver); - $resolver->setDefault('group_name', static function (Options $options) { - return trim($options['name']); - }); + $resolver->setDefault('group_name', static fn(Options $options): string => trim((string) $options['name'])); $resolver->setDefault('inherit', false); diff --git a/src/Form/Permissions/PermissionType.php b/src/Form/Permissions/PermissionType.php index 804fed0a..ab5ee86b 100644 --- a/src/Form/Permissions/PermissionType.php +++ b/src/Form/Permissions/PermissionType.php @@ -33,12 +33,10 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class PermissionType extends AbstractType { - protected PermissionManager $resolver; protected array $perm_structure; - public function __construct(PermissionManager $resolver) + public function __construct(protected PermissionManager $resolver) { - $this->resolver = $resolver; $this->perm_structure = $resolver->getPermissionStructure(); } @@ -46,9 +44,7 @@ class PermissionType extends AbstractType { parent::configureOptions($resolver); - $resolver->setDefault('perm_name', static function (Options $options) { - return $options['name']; - }); + $resolver->setDefault('perm_name', static fn(Options $options) => $options['name']); $resolver->setDefault('label', function (Options $options) { if (!empty($this->perm_structure['perms'][$options['perm_name']]['label'])) { @@ -58,9 +54,7 @@ class PermissionType extends AbstractType return $options['name']; }); - $resolver->setDefault('multi_checkbox', static function (Options $options) { - return !$options['disabled']; - }); + $resolver->setDefault('multi_checkbox', static fn(Options $options) => !$options['disabled']); $resolver->setDefaults([ 'inherit' => false, diff --git a/src/Form/Permissions/PermissionsMapper.php b/src/Form/Permissions/PermissionsMapper.php index c08d2954..2163b26f 100644 --- a/src/Form/Permissions/PermissionsMapper.php +++ b/src/Form/Permissions/PermissionsMapper.php @@ -34,25 +34,20 @@ use Traversable; */ final class PermissionsMapper implements DataMapperInterface { - private PermissionManager $resolver; - private bool $inherit; - - public function __construct(PermissionManager $resolver, bool $inherit = false) + public function __construct(private readonly PermissionManager $resolver, private readonly bool $inherit = false) { - $this->inherit = $inherit; - $this->resolver = $resolver; } /** - * Maps the view data of a compound form to its children. + * Maps the view data of a compound form to its children. * - * The method is responsible for calling {@link FormInterface::setData()} - * on the children of compound forms, defining their underlying model data. + * The method is responsible for calling {@link FormInterface::setData()} + * on the children of compound forms, defining their underlying model data. * * @param mixed $viewData View data of the compound form being initialized - * @param FormInterface[]|Traversable $forms A list of {@link FormInterface} instances + * @param Traversable $forms A list of {@link FormInterface} instances */ - public function mapDataToForms($viewData, $forms): void + public function mapDataToForms($viewData, \Traversable $forms): void { foreach ($forms as $form) { if ($this->inherit) { @@ -73,33 +68,33 @@ final class PermissionsMapper implements DataMapperInterface } /** - * Maps the model data of a list of children forms into the view data of their parent. + * Maps the model data of a list of children forms into the view data of their parent. * - * This is the internal cascade call of FormInterface::submit for compound forms, since they - * cannot be bound to any input nor the request as scalar, but their children may: + * This is the internal cascade call of FormInterface::submit for compound forms, since they + * cannot be bound to any input nor the request as scalar, but their children may: * - * $compoundForm->submit($arrayOfChildrenViewData) - * // inside: - * $childForm->submit($childViewData); - * // for each entry, do the same and/or reverse transform - * $this->dataMapper->mapFormsToData($compoundForm, $compoundInitialViewData) - * // then reverse transform + * $compoundForm->submit($arrayOfChildrenViewData) + * // inside: + * $childForm->submit($childViewData); + * // for each entry, do the same and/or reverse transform + * $this->dataMapper->mapFormsToData($compoundForm, $compoundInitialViewData) + * // then reverse transform * - * When a simple form is submitted the following is happening: + * When a simple form is submitted the following is happening: * - * $simpleForm->submit($submittedViewData) - * // inside: - * $this->viewData = $submittedViewData - * // then reverse transform + * $simpleForm->submit($submittedViewData) + * // inside: + * $this->viewData = $submittedViewData + * // then reverse transform * - * The model data can be an array or an object, so this second argument is always passed - * by reference. + * The model data can be an array or an object, so this second argument is always passed + * by reference. * - * @param FormInterface[]|Traversable $forms A list of {@link FormInterface} instances + * @param Traversable $forms A list of {@link FormInterface} instances * @param mixed $viewData The compound form's view data that get mapped * its children model data */ - public function mapFormsToData($forms, &$viewData): void + public function mapFormsToData(\Traversable $forms, &$viewData): void { if ($this->inherit) { throw new RuntimeException('The permission type is readonly when it is showing read only data!'); diff --git a/src/Form/Permissions/PermissionsType.php b/src/Form/Permissions/PermissionsType.php index e5688729..b0c7ba9d 100644 --- a/src/Form/Permissions/PermissionsType.php +++ b/src/Form/Permissions/PermissionsType.php @@ -33,12 +33,10 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class PermissionsType extends AbstractType { - protected PermissionManager $resolver; protected array $perm_structure; - public function __construct(PermissionManager $resolver) + public function __construct(protected PermissionManager $resolver) { - $this->resolver = $resolver; $this->perm_structure = $resolver->getPermissionStructure(); } diff --git a/src/Form/ProjectSystem/ProjectBOMEntryCollectionType.php b/src/Form/ProjectSystem/ProjectBOMEntryCollectionType.php index 606f23f0..53ec5f70 100644 --- a/src/Form/ProjectSystem/ProjectBOMEntryCollectionType.php +++ b/src/Form/ProjectSystem/ProjectBOMEntryCollectionType.php @@ -1,5 +1,7 @@ setDefaults([ 'entry_type' => ProjectBOMEntryType::class, @@ -27,9 +29,4 @@ class ProjectBOMEntryCollectionType extends AbstractType 'label' => false, ]); } - - public function getBlockPrefix(): string - { - return 'project_bom_entry_collection'; - } -} \ No newline at end of file +} diff --git a/src/Form/ProjectSystem/ProjectBOMEntryType.php b/src/Form/ProjectSystem/ProjectBOMEntryType.php index bac8d679..cac362fb 100644 --- a/src/Form/ProjectSystem/ProjectBOMEntryType.php +++ b/src/Form/ProjectSystem/ProjectBOMEntryType.php @@ -1,5 +1,7 @@ ProjectBOMEntry::class, ]); } - - - public function getBlockPrefix(): string - { - return 'project_bom_entry'; - } -} \ No newline at end of file +} diff --git a/src/Form/ProjectSystem/ProjectBuildType.php b/src/Form/ProjectSystem/ProjectBuildType.php index 3758bb21..82489471 100644 --- a/src/Form/ProjectSystem/ProjectBuildType.php +++ b/src/Form/ProjectSystem/ProjectBuildType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\ProjectSystem; +use Symfony\Bundle\SecurityBundle\Security; +use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; use App\Form\Type\PartLotSelectType; use App\Form\Type\SIUnitType; @@ -34,18 +38,14 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Core\Security; class ProjectBuildType extends AbstractType implements DataMapperInterface { - private Security $security; - - public function __construct(Security $security) + public function __construct(private readonly Security $security) { - $this->security = $security; } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'compound' => true, @@ -53,7 +53,7 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface ]); } - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->setDataMapper($this); @@ -79,10 +79,10 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface $form->add('addBuildsToBuildsPart', CheckboxType::class, [ 'label' => 'project.build.add_builds_to_builds_part', 'required' => false, - 'disabled' => $build_request->getProject()->getBuildPart() === null, + 'disabled' => !$build_request->getProject()->getBuildPart() instanceof Part, ]); - if ($build_request->getProject()->getBuildPart()) { + if ($build_request->getProject()->getBuildPart() instanceof Part) { $form->add('buildsPartLot', PartLotSelectType::class, [ 'label' => 'project.build.builds_part_lot', 'required' => false, @@ -106,7 +106,7 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface }); } - public function mapDataToForms($data, \Traversable $forms) + public function mapDataToForms($data, \Traversable $forms): void { if (!$data instanceof ProjectBuildRequest) { throw new \RuntimeException('Data must be an instance of ' . ProjectBuildRequest::class); @@ -131,7 +131,7 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface } - public function mapFormsToData(\Traversable $forms, &$data) + public function mapFormsToData(\Traversable $forms, &$data): void { if (!$data instanceof ProjectBuildRequest) { throw new \RuntimeException('Data must be an instance of ' . ProjectBuildRequest::class); @@ -155,7 +155,7 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface if (!$lot) { //When the user selected "Create new lot", create a new lot $lot = new PartLot(); $description = 'Build ' . date('Y-m-d H:i:s'); - if (!empty($data->getComment())) { + if ($data->getComment() !== '') { $description .= ' (' . $data->getComment() . ')'; } $lot->setDescription($description); @@ -168,4 +168,4 @@ class ProjectBuildType extends AbstractType implements DataMapperInterface //This has to be set after the builds part lot, so that it can disable the option $data->setAddBuildsToBuildsPart($forms['addBuildsToBuildsPart']->getData()); } -} \ No newline at end of file +} diff --git a/src/Form/Type/BigDecimalMoneyType.php b/src/Form/Type/BigDecimalMoneyType.php index 2fb8d7ee..189416ff 100644 --- a/src/Form/Type/BigDecimalMoneyType.php +++ b/src/Form/Type/BigDecimalMoneyType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Type; use Brick\Math\BigDecimal; @@ -28,7 +30,7 @@ use Symfony\Component\Form\FormBuilderInterface; class BigDecimalMoneyType extends AbstractType implements DataTransformerInterface { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->addModelTransformer($this); } diff --git a/src/Form/Type/BigDecimalNumberType.php b/src/Form/Type/BigDecimalNumberType.php index 8ee0911e..c225f0a4 100644 --- a/src/Form/Type/BigDecimalNumberType.php +++ b/src/Form/Type/BigDecimalNumberType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Type; use Brick\Math\BigDecimal; @@ -28,7 +30,7 @@ use Symfony\Component\Form\FormBuilderInterface; class BigDecimalNumberType extends AbstractType implements DataTransformerInterface { - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->addModelTransformer($this); } diff --git a/src/Form/Type/CurrencyEntityType.php b/src/Form/Type/CurrencyEntityType.php index 46aeac66..07f0a9f8 100644 --- a/src/Form/Type/CurrencyEntityType.php +++ b/src/Form/Type/CurrencyEntityType.php @@ -36,12 +36,9 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class CurrencyEntityType extends StructuralEntityType { - protected ?string $base_currency; - - public function __construct(EntityManagerInterface $em, NodesListBuilder $builder, TranslatorInterface $translator, StructuralEntityChoiceHelper $choiceHelper, ?string $base_currency) + public function __construct(EntityManagerInterface $em, NodesListBuilder $builder, TranslatorInterface $translator, StructuralEntityChoiceHelper $choiceHelper, protected ?string $base_currency) { parent::__construct($em, $builder, $translator, $choiceHelper); - $this->base_currency = $base_currency; } public function configureOptions(OptionsResolver $resolver): void @@ -56,11 +53,7 @@ class CurrencyEntityType extends StructuralEntityType // This options allows you to override the currency shown for the null value $resolver->setDefault('base_currency', null); - $resolver->setDefault('choice_attr', function (Options $options) { - return function ($choice) use ($options) { - return $this->choice_helper->generateChoiceAttrCurrency($choice, $options); - }; - }); + $resolver->setDefault('choice_attr', fn(Options $options) => fn($choice) => $this->choice_helper->generateChoiceAttrCurrency($choice, $options)); $resolver->setDefault('empty_message', function (Options $options) { //By default, we use the global base currency: diff --git a/src/Form/Type/Helper/StructuralEntityChoiceHelper.php b/src/Form/Type/Helper/StructuralEntityChoiceHelper.php index da52b83e..3f7cb7a3 100644 --- a/src/Form/Type/Helper/StructuralEntityChoiceHelper.php +++ b/src/Form/Type/Helper/StructuralEntityChoiceHelper.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Type\Helper; +use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractNamedDBElement; @@ -34,22 +37,15 @@ use Symfony\Contracts\Translation\TranslatorInterface; class StructuralEntityChoiceHelper { - private AttachmentURLGenerator $attachmentURLGenerator; - private TranslatorInterface $translator; - - public function __construct(AttachmentURLGenerator $attachmentURLGenerator, TranslatorInterface $translator) + public function __construct(private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly TranslatorInterface $translator) { - $this->attachmentURLGenerator = $attachmentURLGenerator; - $this->translator = $translator; } /** * Generates the choice attributes for the given AbstractStructuralDBElement. - * @param AbstractNamedDBElement $choice - * @param Options|array $options * @return array|string[] */ - public function generateChoiceAttr(AbstractNamedDBElement $choice, $options): array + public function generateChoiceAttr(AbstractNamedDBElement $choice, Options|array $options): array { $tmp = [ 'data-level' => 0, @@ -69,26 +65,24 @@ class StructuralEntityChoiceHelper $level = $choice->getLevel(); /** @var AbstractStructuralDBElement|null $parent */ $parent = $options['subentities_of'] ?? null; - if (null !== $parent) { + if ($parent instanceof AbstractStructuralDBElement) { $level -= $parent->getLevel() - 1; } - $tmp += [ - 'data-level' => $level, - 'data-parent' => $choice->getParent() ? $choice->getParent()->getFullPath() : null, - 'data-path' => $choice->getFullPath('->'), - ]; + $tmp['data-level'] = $level; + $tmp['data-parent'] = $choice->getParent() instanceof AbstractStructuralDBElement ? $choice->getParent()->getFullPath() : null; + $tmp['data-path'] = $choice->getFullPath('->'); } if ($choice instanceof HasMasterAttachmentInterface) { - $tmp['data-image'] = $choice->getMasterPictureAttachment() ? + $tmp['data-image'] = $choice->getMasterPictureAttachment() instanceof Attachment ? $this->attachmentURLGenerator->getThumbnailURL($choice->getMasterPictureAttachment(), 'thumbnail_xs') : null ; } - if ($choice instanceof AttachmentType && !empty($choice->getFiletypeFilter())) { + if ($choice instanceof AttachmentType && $choice->getFiletypeFilter() !== '') { $tmp += ['data-filetype_filter' => $choice->getFiletypeFilter()]; } @@ -97,37 +91,21 @@ class StructuralEntityChoiceHelper /** * Generates the choice attributes for the given AbstractStructuralDBElement. - * @param Currency $choice - * @param Options|array $options * @return array|string[] */ - public function generateChoiceAttrCurrency(Currency $choice, $options): array + public function generateChoiceAttrCurrency(Currency $choice, Options|array $options): array { $tmp = $this->generateChoiceAttr($choice, $options); + $symbol = $choice->getIsoCode() === null || $choice->getIsoCode() === '' ? null : Currencies::getSymbol($choice->getIsoCode()); + $tmp['data-short'] = $options['short'] ? $symbol : $choice->getName(); - if(!empty($choice->getIsoCode())) { - $symbol = Currencies::getSymbol($choice->getIsoCode()); - } else { - $symbol = null; - } - - if ($options['short']) { - $tmp['data-short'] = $symbol; - } else { - $tmp['data-short'] = $choice->getName(); - } - - $tmp += [ + return $tmp + [ 'data-symbol' => $symbol, ]; - - return $tmp; } /** * Returns the choice label for the given AbstractStructuralDBElement. - * @param AbstractNamedDBElement $choice - * @return string */ public function generateChoiceLabel(AbstractNamedDBElement $choice): string { @@ -136,12 +114,10 @@ class StructuralEntityChoiceHelper /** * Returns the choice value for the given AbstractStructuralDBElement. - * @param AbstractNamedDBElement|null $element - * @return string|int|null */ - public function generateChoiceValue(?AbstractNamedDBElement $element) + public function generateChoiceValue(?AbstractNamedDBElement $element): string|int|null { - if ($element === null) { + if (!$element instanceof AbstractNamedDBElement) { return null; } @@ -162,10 +138,6 @@ class StructuralEntityChoiceHelper return $element->getID(); } - /** - * @param AbstractDBElement $element - * @return string|null - */ public function generateGroupBy(AbstractDBElement $element): ?string { //Show entities that are not added to DB yet separately from other entities @@ -175,4 +147,4 @@ class StructuralEntityChoiceHelper return null; } -} \ No newline at end of file +} diff --git a/src/Form/Type/Helper/StructuralEntityChoiceLoader.php b/src/Form/Type/Helper/StructuralEntityChoiceLoader.php index 4f5347ad..c2d35d92 100644 --- a/src/Form/Type/Helper/StructuralEntityChoiceLoader.php +++ b/src/Form/Type/Helper/StructuralEntityChoiceLoader.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Type\Helper; use App\Repository\StructuralDBElementRepository; @@ -28,17 +30,10 @@ use Symfony\Component\OptionsResolver\Options; class StructuralEntityChoiceLoader extends AbstractChoiceLoader { - private Options $options; - private NodesListBuilder $builder; - private EntityManagerInterface $entityManager; - private ?string $additional_element = null; - public function __construct(Options $options, NodesListBuilder $builder, EntityManagerInterface $entityManager) + public function __construct(private readonly Options $options, private readonly NodesListBuilder $builder, private readonly EntityManagerInterface $entityManager) { - $this->options = $options; - $this->builder = $builder; - $this->entityManager = $entityManager; } protected function loadChoices(): iterable @@ -91,4 +86,4 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader return $this->additional_element; } -} \ No newline at end of file +} diff --git a/src/Form/Type/MasterPictureAttachmentType.php b/src/Form/Type/MasterPictureAttachmentType.php index 7aba9cc6..b5edbd55 100644 --- a/src/Form/Type/MasterPictureAttachmentType.php +++ b/src/Form/Type/MasterPictureAttachmentType.php @@ -42,32 +42,28 @@ class MasterPictureAttachmentType extends AbstractType $resolver->setDefaults([ 'filter' => 'picture', 'choice_translation_domain' => false, - 'choice_attr' => static function (Options $options) { - return static function ($choice, $key, $value) use ($options) { - /** @var Attachment $choice */ - $tmp = ['data-subtext' => $choice->getFilename() ?? 'URL']; + 'choice_attr' => static fn(Options $options) => static function ($choice, $key, $value) use ($options) { + /** @var Attachment $choice */ + $tmp = ['data-subtext' => $choice->getFilename() ?? 'URL']; - if ('picture' === $options['filter'] && !$choice->isPicture()) { - $tmp += ['disabled' => 'disabled']; - } elseif ('3d_model' === $options['filter'] && !$choice->is3DModel()) { - $tmp += ['disabled' => 'disabled']; - } + if ('picture' === $options['filter'] && !$choice->isPicture()) { + $tmp += ['disabled' => 'disabled']; + } elseif ('3d_model' === $options['filter'] && !$choice->is3DModel()) { + $tmp += ['disabled' => 'disabled']; + } - return $tmp; - }; + return $tmp; }, 'choice_label' => 'name', - 'choice_loader' => static function (Options $options) { - return new CallbackChoiceLoader( - static function () use ($options) { - $entity = $options['entity']; - if (!$entity instanceof AttachmentContainingDBElement) { - throw new RuntimeException('$entity must have Attachments! (be of type AttachmentContainingDBElement)'); - } + 'choice_loader' => static fn(Options $options) => new CallbackChoiceLoader( + static function () use ($options) { + $entity = $options['entity']; + if (!$entity instanceof AttachmentContainingDBElement) { + throw new RuntimeException('$entity must have Attachments! (be of type AttachmentContainingDBElement)'); + } - return $entity->getAttachments()->toArray(); - }); - }, + return $entity->getAttachments()->toArray(); + }), ]); $resolver->setAllowedValues('filter', ['', 'picture', '3d_model']); diff --git a/src/Form/Type/PartLotSelectType.php b/src/Form/Type/PartLotSelectType.php index 76e31ecb..8eff5122 100644 --- a/src/Form/Type/PartLotSelectType.php +++ b/src/Form/Type/PartLotSelectType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Type; +use App\Entity\Parts\Storelocation; use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; use Doctrine\ORM\EntityRepository; @@ -36,24 +39,18 @@ class PartLotSelectType extends AbstractType return EntityType::class; } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setRequired('part'); $resolver->setAllowedTypes('part', Part::class); $resolver->setDefaults([ 'class' => PartLot::class, - 'choice_label' => ChoiceList::label($this, static function (PartLot $part_lot) { - return ($part_lot->getStorageLocation() ? $part_lot->getStorageLocation()->getFullPath() : '') - . ' (' . $part_lot->getName() . '): ' . $part_lot->getAmount(); - }), - 'query_builder' => function (Options $options) { - return static function (EntityRepository $er) use ($options) { - return $er->createQueryBuilder('l') - ->where('l.part = :part') - ->setParameter('part', $options['part']); - }; - } + 'choice_label' => ChoiceList::label($this, static fn(PartLot $part_lot): string => ($part_lot->getStorageLocation() instanceof Storelocation ? $part_lot->getStorageLocation()->getFullPath() : '') + . ' (' . $part_lot->getName() . '): ' . $part_lot->getAmount()), + 'query_builder' => fn(Options $options) => static fn(EntityRepository $er) => $er->createQueryBuilder('l') + ->where('l.part = :part') + ->setParameter('part', $options['part']) ]); } -} \ No newline at end of file +} diff --git a/src/Form/Type/PartSelectType.php b/src/Form/Type/PartSelectType.php index 08cf688c..34b8fc7c 100644 --- a/src/Form/Type/PartSelectType.php +++ b/src/Form/Type/PartSelectType.php @@ -1,7 +1,12 @@ urlGenerator = $urlGenerator; - $this->em = $em; - $this->previewGenerator = $previewGenerator; - $this->attachmentURLGenerator = $attachmentURLGenerator; } - public function buildForm(FormBuilderInterface $builder, array $options) + public function buildForm(FormBuilderInterface $builder, array $options): void { //At initialization, we have to fill the form element with our selected data, so the user can see it $builder->addEventListener(FormEvents::PRE_SET_DATA, function (PreSetDataEvent $event) { @@ -73,7 +68,7 @@ class PartSelectType extends AbstractType implements DataMapperInterface $builder->setDataMapper($this); } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'class' => Part::class, @@ -96,10 +91,10 @@ class PartSelectType extends AbstractType implements DataMapperInterface $resolver->setDefaults([ //Prefill the selected choice with the needed data, so the user can see it without an additional Ajax request 'choice_attr' => ChoiceList::attr($this, function (?Part $part) { - if($part) { + if($part instanceof Part) { //Determine the picture to show: $preview_attachment = $this->previewGenerator->getTablePreviewAttachment($part); - if ($preview_attachment !== null) { + if ($preview_attachment instanceof Attachment) { $preview_url = $this->attachmentURLGenerator->getThumbnailURL($preview_attachment, 'thumbnail_sm'); } else { @@ -107,31 +102,26 @@ class PartSelectType extends AbstractType implements DataMapperInterface } } - return $part ? [ + return $part instanceof Part ? [ 'data-description' => mb_strimwidth($part->getDescription(), 0, 127, '...'), - 'data-category' => $part->getCategory() ? $part->getCategory()->getName() : '', - 'data-footprint' => $part->getFootprint() ? $part->getFootprint()->getName() : '', + 'data-category' => $part->getCategory() instanceof Category ? $part->getCategory()->getName() : '', + 'data-footprint' => $part->getFootprint() instanceof Footprint ? $part->getFootprint()->getName() : '', 'data-image' => $preview_url, ] : []; }) ]); } - public function getBlockPrefix(): string - { - return 'part_select'; - } - - public function mapDataToForms($data, $forms) + public function mapDataToForms($data, \Traversable $forms): void { $form = current(iterator_to_array($forms, false)); $form->setData($data); } - public function mapFormsToData($forms, &$data) + public function mapFormsToData(\Traversable $forms, &$data): void { $form = current(iterator_to_array($forms, false)); $data = $form->getData(); } -} \ No newline at end of file +} diff --git a/src/Form/Type/RichTextEditorType.php b/src/Form/Type/RichTextEditorType.php index a572fa2e..50683b0c 100644 --- a/src/Form/Type/RichTextEditorType.php +++ b/src/Form/Type/RichTextEditorType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Type; use Symfony\Component\Form\AbstractType; @@ -28,7 +30,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class RichTextEditorType extends AbstractType { - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { parent::configureOptions($resolver); // TODO: Change the autogenerated stub @@ -39,12 +41,7 @@ class RichTextEditorType extends AbstractType } - public function getBlockPrefix(): string - { - return 'rich_text_editor'; - } - - public function finishView(FormView $view, FormInterface $form, array $options) + public function finishView(FormView $view, FormInterface $form, array $options): void { $view->vars['attr'] = array_merge($view->vars['attr'], $this->optionsToAttrArray($options)); @@ -53,22 +50,17 @@ class RichTextEditorType extends AbstractType protected function optionsToAttrArray(array $options): array { - $tmp = []; - - //Set novalidate attribute, or we will get problems that form can not be submitted as textarea is not focusable - $tmp['novalidate'] = 'novalidate'; - - $tmp['data-mode'] = $options['mode']; - - //Add our data-controller element to the textarea - $tmp['data-controller'] = 'elements--ckeditor'; - - - return $tmp; + return [ + //Set novalidate attribute, or we will get problems that form can not be submitted as textarea is not focusable + 'novalidate' => 'novalidate', + 'data-mode' => $options['mode'], + //Add our data-controller element to the textarea + 'data-controller' => 'elements--ckeditor', + ]; } public function getParent(): string { return TextareaType::class; } -} \ No newline at end of file +} diff --git a/src/Form/Type/SIUnitType.php b/src/Form/Type/SIUnitType.php index bfec23e2..2f0138f0 100644 --- a/src/Form/Type/SIUnitType.php +++ b/src/Form/Type/SIUnitType.php @@ -38,11 +38,8 @@ use Traversable; final class SIUnitType extends AbstractType implements DataMapperInterface { - protected SIFormatter $si_formatter; - - public function __construct(SIFormatter $SIFormatter) + public function __construct(protected SIFormatter $si_formatter) { - $this->si_formatter = $SIFormatter; } public function configureOptions(OptionsResolver $resolver): void @@ -91,7 +88,7 @@ final class SIUnitType extends AbstractType implements DataMapperInterface $resolver->setDefaults([ 'min' => 0, 'max' => '', - 'step' => static function (Options $options) { + 'step' => static function (Options $options): int|string { if (true === $options['is_integer']) { return 1; } @@ -138,7 +135,7 @@ final class SIUnitType extends AbstractType implements DataMapperInterface //Check if we need to make this thing small if (isset($options['attr']['class'])) { - $view->vars['sm'] = false !== strpos($options['attr']['class'], 'form-control-sm'); + $view->vars['sm'] = str_contains((string) $options['attr']['class'], 'form-control-sm'); } $view->vars['unit'] = $options['unit']; @@ -146,17 +143,17 @@ final class SIUnitType extends AbstractType implements DataMapperInterface } /** - * Maps the view data of a compound form to its children. + * Maps the view data of a compound form to its children. * - * The method is responsible for calling {@link FormInterface::setData()} - * on the children of compound forms, defining their underlying model data. + * The method is responsible for calling {@link FormInterface::setData()} + * on the children of compound forms, defining their underlying model data. * * @param mixed $viewData View data of the compound form being initialized - * @param FormInterface[]|Traversable $forms A list of {@link FormInterface} instances + * @param Traversable $forms A list of {@link FormInterface} instances * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ - public function mapDataToForms($viewData, $forms): void + public function mapDataToForms($viewData, \Traversable $forms): void { $forms = iterator_to_array($forms); @@ -179,35 +176,35 @@ final class SIUnitType extends AbstractType implements DataMapperInterface } /** - * Maps the model data of a list of children forms into the view data of their parent. + * Maps the model data of a list of children forms into the view data of their parent. * - * This is the internal cascade call of FormInterface::submit for compound forms, since they - * cannot be bound to any input nor the request as scalar, but their children may: + * This is the internal cascade call of FormInterface::submit for compound forms, since they + * cannot be bound to any input nor the request as scalar, but their children may: * - * $compoundForm->submit($arrayOfChildrenViewData) - * // inside: - * $childForm->submit($childViewData); - * // for each entry, do the same and/or reverse transform - * $this->dataMapper->mapFormsToData($compoundForm, $compoundInitialViewData) - * // then reverse transform + * $compoundForm->submit($arrayOfChildrenViewData) + * // inside: + * $childForm->submit($childViewData); + * // for each entry, do the same and/or reverse transform + * $this->dataMapper->mapFormsToData($compoundForm, $compoundInitialViewData) + * // then reverse transform * - * When a simple form is submitted the following is happening: + * When a simple form is submitted the following is happening: * - * $simpleForm->submit($submittedViewData) - * // inside: - * $this->viewData = $submittedViewData - * // then reverse transform + * $simpleForm->submit($submittedViewData) + * // inside: + * $this->viewData = $submittedViewData + * // then reverse transform * - * The model data can be an array or an object, so this second argument is always passed - * by reference. + * The model data can be an array or an object, so this second argument is always passed + * by reference. * - * @param FormInterface[]|Traversable $forms A list of {@link FormInterface} instances + * @param Traversable $forms A list of {@link FormInterface} instances * @param mixed $viewData The compound form's view data that get mapped * its children model data * * @throws Exception\UnexpectedTypeException if the type of the data parameter is not supported */ - public function mapFormsToData($forms, &$viewData): void + public function mapFormsToData(\Traversable $forms, &$viewData): void { //Convert both fields to a single float value. diff --git a/src/Form/Type/StructuralEntityType.php b/src/Form/Type/StructuralEntityType.php index 9c489b47..8afb6ce2 100644 --- a/src/Form/Type/StructuralEntityType.php +++ b/src/Form/Type/StructuralEntityType.php @@ -44,21 +44,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class StructuralEntityType extends AbstractType { - protected EntityManagerInterface $em; - protected TranslatorInterface $translator; - protected StructuralEntityChoiceHelper $choice_helper; - - /** - * @var NodesListBuilder - */ - protected NodesListBuilder $builder; - - public function __construct(EntityManagerInterface $em, NodesListBuilder $builder, TranslatorInterface $translator, StructuralEntityChoiceHelper $choice_helper) + public function __construct(protected EntityManagerInterface $em, protected NodesListBuilder $builder, protected TranslatorInterface $translator, protected StructuralEntityChoiceHelper $choice_helper) { - $this->em = $em; - $this->builder = $builder; - $this->translator = $translator; - $this->choice_helper = $choice_helper; } public function buildForm(FormBuilderInterface $builder, array $options): void @@ -81,11 +68,7 @@ class StructuralEntityType extends AbstractType }); $builder->addModelTransformer(new CallbackTransformer( - function ($value) use ($options) { - return $this->modelTransform($value, $options); - }, function ($value) use ($options) { - return $this->modelReverseTransform($value, $options); - })); + fn($value) => $this->modelTransform($value, $options), fn($value) => $this->modelReverseTransform($value, $options))); } public function configureOptions(OptionsResolver $resolver): void @@ -96,25 +79,11 @@ class StructuralEntityType extends AbstractType 'show_fullpath_in_subtext' => true, //When this is enabled, the full path will be shown in subtext 'subentities_of' => null, //Only show entities with the given parent class 'disable_not_selectable' => false, //Disable entries with not selectable property - 'choice_value' => function (?AbstractNamedDBElement $element) { - return $this->choice_helper->generateChoiceValue($element); - }, //Use the element id as option value and for comparing items - 'choice_loader' => function (Options $options) { - return new StructuralEntityChoiceLoader($options, $this->builder, $this->em); - }, - 'choice_label' => function (Options $options) { - return function ($choice, $key, $value) { - return $this->choice_helper->generateChoiceLabel($choice); - }; - }, - 'choice_attr' => function (Options $options) { - return function ($choice, $key, $value) use ($options) { - return $this->choice_helper->generateChoiceAttr($choice, $options); - }; - }, - 'group_by' => function (AbstractNamedDBElement $element) { - return $this->choice_helper->generateGroupBy($element); - }, + 'choice_value' => fn(?AbstractNamedDBElement $element) => $this->choice_helper->generateChoiceValue($element), //Use the element id as option value and for comparing items + 'choice_loader' => fn(Options $options) => new StructuralEntityChoiceLoader($options, $this->builder, $this->em), + 'choice_label' => fn(Options $options) => fn($choice, $key, $value) => $this->choice_helper->generateChoiceLabel($choice), + 'choice_attr' => fn(Options $options) => fn($choice, $key, $value) => $this->choice_helper->generateChoiceAttr($choice, $options), + 'group_by' => fn(AbstractNamedDBElement $element) => $this->choice_helper->generateGroupBy($element), 'choice_translation_domain' => false, //Don't translate the entity names ]); diff --git a/src/Form/Type/ThemeChoiceType.php b/src/Form/Type/ThemeChoiceType.php index 96b47510..7cdc0aa9 100644 --- a/src/Form/Type/ThemeChoiceType.php +++ b/src/Form/Type/ThemeChoiceType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Type; use Symfony\Component\Form\AbstractType; @@ -26,11 +28,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class ThemeChoiceType extends AbstractType { - private array $available_themes; - - public function __construct(array $available_themes) + public function __construct(private readonly array $available_themes) { - $this->available_themes = $available_themes; } public function getParent(): string @@ -38,16 +37,14 @@ class ThemeChoiceType extends AbstractType return ChoiceType::class; } - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'choices' => $this->available_themes, - 'choice_label' => static function ($entity, $key, $value) { - return $value; - }, + 'choice_label' => static fn($entity, $key, $value) => $value, 'choice_translation_domain' => false, 'placeholder' => 'user_settings.theme.placeholder' ]); } -} \ No newline at end of file +} diff --git a/src/Form/Type/TriStateCheckboxType.php b/src/Form/Type/TriStateCheckboxType.php index 6e8dafc4..df4b0f3a 100644 --- a/src/Form/Type/TriStateCheckboxType.php +++ b/src/Form/Type/TriStateCheckboxType.php @@ -147,17 +147,11 @@ final class TriStateCheckboxType extends AbstractType implements DataTransformer */ public function reverseTransform($value) { - switch ($value) { - case 'true': - return true; - case 'false': - return false; - case 'indeterminate': - case 'null': - case '': - return null; - default: - throw new InvalidArgumentException('Invalid value encountered!: '.$value); - } + return match ($value) { + 'true' => true, + 'false' => false, + 'indeterminate', 'null', '' => null, + default => throw new InvalidArgumentException('Invalid value encountered!: '.$value), + }; } } diff --git a/src/Form/Type/UserSelectType.php b/src/Form/Type/UserSelectType.php index 97d8e26d..cc16d724 100644 --- a/src/Form/Type/UserSelectType.php +++ b/src/Form/Type/UserSelectType.php @@ -1,4 +1,7 @@ . */ - namespace App\Form\Type; use App\Entity\UserSystem\User; @@ -28,15 +30,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class UserSelectType extends AbstractType { - public function configureOptions(OptionsResolver $resolver) + public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefaults([ 'class' => User::class, - 'choice_label' => function (Options $options) { - return function (User $choice, $key, $value) { - return $choice->getFullName(true); - }; - }, + 'choice_label' => fn(Options $options) => fn(User $choice, $key, $value) => $choice->getFullName(true), ]); } @@ -44,4 +42,4 @@ class UserSelectType extends AbstractType { return StructuralEntityType::class; } -} \ No newline at end of file +} diff --git a/src/Form/UserAdminForm.php b/src/Form/UserAdminForm.php index 06347306..f8fcc7c6 100644 --- a/src/Form/UserAdminForm.php +++ b/src/Form/UserAdminForm.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Form; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Base\AbstractNamedDBElement; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; @@ -43,16 +44,12 @@ use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TimezoneType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Validator\Constraints\Length; class UserAdminForm extends AbstractType { - protected Security $security; - - public function __construct(Security $security) + public function __construct(protected Security $security) { - $this->security = $security; } public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Form/UserSettingsType.php b/src/Form/UserSettingsType.php index 37f7b7e0..53ca8cf8 100644 --- a/src/Form/UserSettingsType.php +++ b/src/Form/UserSettingsType.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Form; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\UserSystem\User; use App\Form\Type\CurrencyEntityType; use App\Form\Type\RichTextEditorType; @@ -39,18 +40,12 @@ use Symfony\Component\Form\Extension\Core\Type\TimezoneType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Validator\Constraints\File; class UserSettingsType extends AbstractType { - protected Security $security; - protected bool $demo_mode; - - public function __construct(Security $security, bool $demo_mode) + public function __construct(protected Security $security, protected bool $demo_mode) { - $this->security = $security; - $this->demo_mode = $demo_mode; } public function buildForm(FormBuilderInterface $builder, array $options): void diff --git a/src/Helpers/BBCodeToMarkdownConverter.php b/src/Helpers/BBCodeToMarkdownConverter.php index f800006b..922e6a7e 100644 --- a/src/Helpers/BBCodeToMarkdownConverter.php +++ b/src/Helpers/BBCodeToMarkdownConverter.php @@ -25,6 +25,9 @@ namespace App\Helpers; use League\HTMLToMarkdown\HtmlConverter; use s9e\TextFormatter\Bundles\Forum as TextFormatter; +/** + * @see \App\Tests\Helpers\BBCodeToMarkdownConverterTest + */ class BBCodeToMarkdownConverter { protected HtmlConverter $html_to_markdown; diff --git a/src/Helpers/LabelResponse.php b/src/Helpers/LabelResponse.php index 1dbb947b..98451e2f 100644 --- a/src/Helpers/LabelResponse.php +++ b/src/Helpers/LabelResponse.php @@ -54,7 +54,7 @@ class LabelResponse extends Response parent::__construct($content, $status, $headers); } - public function setContent($content): self + public function setContent($content): static { parent::setContent($content); @@ -64,7 +64,7 @@ class LabelResponse extends Response return $this; } - public function prepare(Request $request): self + public function prepare(Request $request): static { parent::prepare($request); @@ -110,7 +110,7 @@ class LabelResponse extends Response */ public function setContentDisposition(string $disposition, string $filename, string $filenameFallback = ''): self { - if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) { + if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || str_contains($filename, '%'))) { $encoding = mb_detect_encoding($filename, null, true) ?: '8bit'; for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { diff --git a/src/Helpers/Projects/ProjectBuildRequest.php b/src/Helpers/Projects/ProjectBuildRequest.php index 4cb942d1..36035744 100644 --- a/src/Helpers/Projects/ProjectBuildRequest.php +++ b/src/Helpers/Projects/ProjectBuildRequest.php @@ -1,4 +1,7 @@ . */ - namespace App\Helpers\Projects; +use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; use App\Validator\Constraints\ProjectSystem\ValidProjectBuildRequest; /** - * @ValidProjectBuildRequest() + * @see \App\Tests\Helpers\Projects\ProjectBuildRequestTest */ +#[ValidProjectBuildRequest] final class ProjectBuildRequest { - private Project $project; - private int $number_of_builds; + private readonly int $number_of_builds; /** * @var array @@ -48,19 +51,17 @@ final class ProjectBuildRequest * @param Project $project The project that should be build * @param int $number_of_builds The number of builds that should be created */ - public function __construct(Project $project, int $number_of_builds) + public function __construct(private readonly Project $project, int $number_of_builds) { if ($number_of_builds < 1) { throw new \InvalidArgumentException('Number of builds must be at least 1!'); } - - $this->project = $project; $this->number_of_builds = $number_of_builds; $this->initializeArray(); //By default, use the first available lot of builds part if there is one. - if($project->getBuildPart() !== null) { + if($project->getBuildPart() instanceof Part) { $this->add_build_to_builds_part = true; foreach( $project->getBuildPart()->getPartLots() as $lot) { if (!$lot->isInstockUnknown()) { @@ -89,8 +90,6 @@ final class ProjectBuildRequest /** * Ensure that the projectBOMEntry belongs to the project, otherwise throw an exception. - * @param ProjectBOMEntry $entry - * @return void */ private function ensureBOMEntryValid(ProjectBOMEntry $entry): void { @@ -101,7 +100,6 @@ final class ProjectBuildRequest /** * Returns the partlot where the builds should be added to, or null if it should not be added to any lot. - * @return PartLot|null */ public function getBuildsPartLot(): ?PartLot { @@ -110,7 +108,6 @@ final class ProjectBuildRequest /** * Return if the builds should be added to the builds part of this project as new stock - * @return bool */ public function getAddBuildsToBuildsPart(): bool { @@ -119,7 +116,6 @@ final class ProjectBuildRequest /** * Set if the builds should be added to the builds part of this project as new stock - * @param bool $new_value * @return $this */ public function setAddBuildsToBuildsPart(bool $new_value): self @@ -136,17 +132,16 @@ final class ProjectBuildRequest /** * Set the partlot where the builds should be added to, or null if it should not be added to any lot. * The part lot must belong to the project build part, or an exception is thrown! - * @param PartLot|null $new_part_lot * @return $this */ public function setBuildsPartLot(?PartLot $new_part_lot): self { //Ensure that this new_part_lot belongs to the project - if (($new_part_lot !== null && $new_part_lot->getPart() !== $this->project->getBuildPart()) || $this->project->getBuildPart() === null) { + if (($new_part_lot instanceof PartLot && $new_part_lot->getPart() !== $this->project->getBuildPart()) || !$this->project->getBuildPart() instanceof Part) { throw new \InvalidArgumentException('The given part lot does not belong to the projects build part!'); } - if ($new_part_lot !== null) { + if ($new_part_lot instanceof PartLot) { $this->setAddBuildsToBuildsPart(true); } @@ -157,7 +152,6 @@ final class ProjectBuildRequest /** * Returns the comment where the user can write additional information about the build. - * @return string */ public function getComment(): string { @@ -166,7 +160,6 @@ final class ProjectBuildRequest /** * Sets the comment where the user can write additional information about the build. - * @param string $comment */ public function setComment(string $comment): void { @@ -176,16 +169,13 @@ final class ProjectBuildRequest /** * Returns the amount of parts that should be withdrawn from the given lot for the corresponding BOM entry. * @param PartLot|int $lot The part lot (or the ID of the part lot) for which the withdrawal amount should be got - * @return float */ - public function getLotWithdrawAmount($lot): float + public function getLotWithdrawAmount(PartLot|int $lot): float { if ($lot instanceof PartLot) { $lot_id = $lot->getID(); - } elseif (is_int($lot)) { + } else { // Then it must be an int $lot_id = $lot; - } else { - throw new \InvalidArgumentException('The given lot must be an instance of PartLot or an ID of a PartLot!'); } if (! array_key_exists($lot_id, $this->withdraw_amounts)) { @@ -198,10 +188,9 @@ final class ProjectBuildRequest /** * Sets the amount of parts that should be withdrawn from the given lot for the corresponding BOM entry. * @param PartLot|int $lot The part lot (or the ID of the part lot) for which the withdrawal amount should be got - * @param float $amount * @return $this */ - public function setLotWithdrawAmount($lot, float $amount): self + public function setLotWithdrawAmount(PartLot|int $lot, float $amount): self { if ($lot instanceof PartLot) { $lot_id = $lot->getID(); @@ -218,8 +207,6 @@ final class ProjectBuildRequest /** * Returns the sum of all withdraw amounts for the given BOM entry. - * @param ProjectBOMEntry $entry - * @return float */ public function getWithdrawAmountSum(ProjectBOMEntry $entry): float { @@ -239,14 +226,13 @@ final class ProjectBuildRequest /** * Returns the number of available lots to take stock from for the given BOM entry. - * @param ProjectBOMEntry $projectBOMEntry * @return PartLot[]|null Returns null if the entry is a non-part BOM entry */ public function getPartLotsForBOMEntry(ProjectBOMEntry $projectBOMEntry): ?array { $this->ensureBOMEntryValid($projectBOMEntry); - if ($projectBOMEntry->getPart() === null) { + if (!$projectBOMEntry->getPart() instanceof Part) { return null; } @@ -256,8 +242,6 @@ final class ProjectBuildRequest /** * Returns the needed amount of parts for the given BOM entry. - * @param ProjectBOMEntry $entry - * @return float */ public function getNeededAmountForBOMEntry(ProjectBOMEntry $entry): float { @@ -281,14 +265,11 @@ final class ProjectBuildRequest */ public function getPartBomEntries(): array { - return $this->project->getBomEntries()->filter(function (ProjectBOMEntry $entry) { - return $entry->isPartBomEntry(); - })->toArray(); + return $this->project->getBomEntries()->filter(fn(ProjectBOMEntry $entry) => $entry->isPartBomEntry())->toArray(); } /** * Returns which project should be build - * @return Project */ public function getProject(): Project { @@ -297,10 +278,9 @@ final class ProjectBuildRequest /** * Returns the number of builds that should be created. - * @return int */ public function getNumberOfBuilds(): int { return $this->number_of_builds; } -} \ No newline at end of file +} diff --git a/src/Helpers/Trees/TreeViewNode.php b/src/Helpers/Trees/TreeViewNode.php index 85053239..0c5fcdce 100644 --- a/src/Helpers/Trees/TreeViewNode.php +++ b/src/Helpers/Trees/TreeViewNode.php @@ -30,10 +30,6 @@ use JsonSerializable; */ final class TreeViewNode implements JsonSerializable { - private string $text; - private ?string $href; - private ?array $nodes; - private ?TreeViewNodeState $state = null; private ?array $tags = null; @@ -51,12 +47,8 @@ final class TreeViewNode implements JsonSerializable * @param array|null $nodes An array containing other TreeViewNodes. They will be used as children nodes of the * newly created nodes. Set to null, if it should not have children. */ - public function __construct(string $text, ?string $href = null, ?array $nodes = null) + public function __construct(private string $text, private ?string $href = null, private ?array $nodes = null) { - $this->text = $text; - $this->href = $href; - $this->nodes = $nodes; - //$this->state = new TreeViewNodeState(); } @@ -94,8 +86,6 @@ final class TreeViewNode implements JsonSerializable * Sets the node text. * * @param string $text the new node text - * - * @return TreeViewNode */ public function setText(string $text): self { @@ -116,8 +106,6 @@ final class TreeViewNode implements JsonSerializable * Sets the href link. * * @param string|null $href the new href link - * - * @return TreeViewNode */ public function setHref(?string $href): self { @@ -140,8 +128,6 @@ final class TreeViewNode implements JsonSerializable * Sets the children nodes. * * @param array|null $nodes The new children nodes - * - * @return TreeViewNode */ public function setNodes(?array $nodes): self { @@ -165,7 +151,7 @@ final class TreeViewNode implements JsonSerializable public function setDisabled(?bool $disabled): self { //Lazy loading of state, so it does not need to get serialized and transfered, when it is empty. - if (null === $this->state) { + if (!$this->state instanceof TreeViewNodeState) { $this->state = new TreeViewNodeState(); } @@ -177,7 +163,7 @@ final class TreeViewNode implements JsonSerializable public function setSelected(?bool $selected): self { //Lazy loading of state, so it does not need to get serialized and transfered, when it is empty. - if (null === $this->state) { + if (!$this->state instanceof TreeViewNodeState) { $this->state = new TreeViewNodeState(); } @@ -189,7 +175,7 @@ final class TreeViewNode implements JsonSerializable public function setExpanded(?bool $selected = true): self { //Lazy loading of state, so it does not need to get serialized and transfered, when it is empty. - if (null === $this->state) { + if (!$this->state instanceof TreeViewNodeState) { $this->state = new TreeViewNodeState(); } @@ -215,17 +201,11 @@ final class TreeViewNode implements JsonSerializable return $this; } - /** - * @return string|null - */ public function getIcon(): ?string { return $this->icon; } - /** - * @param string|null $icon - */ public function setIcon(?string $icon): self { $this->icon = $icon; @@ -252,7 +232,7 @@ final class TreeViewNode implements JsonSerializable $ret['nodes'] = $this->nodes; } - if (null !== $this->state) { + if ($this->state instanceof TreeViewNodeState) { $ret['state'] = $this->state; } diff --git a/src/Helpers/Trees/TreeViewNodeIterator.php b/src/Helpers/Trees/TreeViewNodeIterator.php index 073218c0..ab8b4907 100644 --- a/src/Helpers/Trees/TreeViewNodeIterator.php +++ b/src/Helpers/Trees/TreeViewNodeIterator.php @@ -40,7 +40,7 @@ final class TreeViewNodeIterator extends ArrayIterator implements RecursiveItera /** @var TreeViewNode $element */ $element = $this->current(); - return !empty($element->getNodes()); + return $element->getNodes() !== null && $element->getNodes() !== []; } public function getChildren(): TreeViewNodeIterator diff --git a/src/Kernel.php b/src/Kernel.php index a406b6c8..97c7a69e 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -1,4 +1,7 @@ . */ - namespace App; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; diff --git a/src/Migration/AbstractMultiPlatformMigration.php b/src/Migration/AbstractMultiPlatformMigration.php index 324eeb8d..54e3b529 100644 --- a/src/Migration/AbstractMultiPlatformMigration.php +++ b/src/Migration/AbstractMultiPlatformMigration.php @@ -1,4 +1,7 @@ . */ - namespace App\Migration; use Doctrine\DBAL\Connection; @@ -30,14 +32,11 @@ use Psr\Log\LoggerInterface; abstract class AbstractMultiPlatformMigration extends AbstractMigration { - public const ADMIN_PW_LENGTH = 10; + final public const ADMIN_PW_LENGTH = 10; protected string $admin_pw = ''; - protected LoggerInterface $logger; - - public function __construct(Connection $connection, LoggerInterface $logger) + public function __construct(Connection $connection, protected LoggerInterface $logger) { - $this->logger = $logger; parent::__construct($connection, $logger); } @@ -45,34 +44,22 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration { $db_type = $this->getDatabaseType(); - switch ($db_type) { - case 'mysql': - $this->mySQLUp($schema); - break; - case 'sqlite': - $this->sqLiteUp($schema); - break; - default: - $this->abortIf(true, "Database type '$db_type' is not supported!"); - break; - } + match ($db_type) { + 'mysql' => $this->mySQLUp($schema), + 'sqlite' => $this->sqLiteUp($schema), + default => $this->abortIf(true, "Database type '$db_type' is not supported!"), + }; } public function down(Schema $schema): void { $db_type = $this->getDatabaseType(); - switch ($db_type) { - case 'mysql': - $this->mySQLDown($schema); - break; - case 'sqlite': - $this->sqLiteDown($schema); - break; - default: - $this->abortIf(true, "Database type is not supported!"); - break; - } + match ($db_type) { + 'mysql' => $this->mySQLDown($schema), + 'sqlite' => $this->sqLiteDown($schema), + default => $this->abortIf(true, "Database type is not supported!"), + }; } /** @@ -91,7 +78,7 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration return 0; } return (int) $version; - } catch (Exception $dBALException) { + } catch (Exception) { //when the table was not found, we can proceed, because we have an empty DB! return 0; } @@ -103,7 +90,7 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration */ public function getInitalAdminPW(): string { - if (empty($this->admin_pw)) { + if ($this->admin_pw === '') { if (!empty($_ENV['INITIAL_ADMIN_PW'])) { $this->admin_pw = $_ENV['INITIAL_ADMIN_PW']; } else { @@ -112,14 +99,14 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration } //As we don't have access to container, just use the default PHP pw hash function - return password_hash($this->admin_pw, PASSWORD_DEFAULT); + return password_hash((string) $this->admin_pw, PASSWORD_DEFAULT); } public function postUp(Schema $schema): void { parent::postUp($schema); - if (!empty($this->admin_pw)) { + if ($this->admin_pw !== '') { $this->logger->warning(''); $this->logger->warning('The initial password for the "admin" user is: '.$this->admin_pw.''); $this->logger->warning(''); @@ -129,8 +116,6 @@ abstract class AbstractMultiPlatformMigration extends AbstractMigration /** * Checks if a foreign key on a table exists in the database. * This method is only supported for MySQL/MariaDB databases yet! - * @param string $table - * @param string $fk_name * @return bool Returns true, if the foreign key exists * @throws Exception */ diff --git a/src/Repository/AbstractPartsContainingRepository.php b/src/Repository/AbstractPartsContainingRepository.php index b42d075f..3a389610 100644 --- a/src/Repository/AbstractPartsContainingRepository.php +++ b/src/Repository/AbstractPartsContainingRepository.php @@ -1,4 +1,7 @@ . */ - namespace App\Repository; use App\Entity\Base\AbstractPartsContainingDBElement; @@ -25,6 +27,10 @@ use App\Entity\Base\PartsContainingRepositoryInterface; use App\Entity\Parts\Part; use InvalidArgumentException; +/** + * @template TEntityClass of AbstractPartsContainingDBElement + * @extends StructuralDBElementRepository + */ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepository implements PartsContainingRepositoryInterface { /** @var int The maximum number of levels for which we can recurse before throwing an error */ @@ -51,7 +57,6 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo /** * Returns the count of the parts associated with this element and all its children. * Please be aware that this function is pretty slow on large trees! - * @param AbstractPartsContainingDBElement $element * @return int */ public function getPartsCountRecursive(AbstractPartsContainingDBElement $element): int @@ -64,8 +69,6 @@ abstract class AbstractPartsContainingRepository extends StructuralDBElementRepo * This function is used to limit the recursion depth (remaining_depth is decreased on each call). * If the recursion limit is reached (remaining_depth <= 0), a RuntimeException is thrown. * @internal This function is not intended to be called directly, use getPartsCountRecursive() instead. - * @param AbstractPartsContainingDBElement $element - * @param int $remaining_depth * @return int */ protected function getPartsCountRecursiveWithDepthN(AbstractPartsContainingDBElement $element, int $remaining_depth): int diff --git a/src/Repository/AttachmentRepository.php b/src/Repository/AttachmentRepository.php index 69842352..240ab058 100644 --- a/src/Repository/AttachmentRepository.php +++ b/src/Repository/AttachmentRepository.php @@ -41,9 +41,14 @@ declare(strict_types=1); namespace App\Repository; +use App\Entity\Attachments\Attachment; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\NoResultException; +/** + * @template TEntityClass of Attachment + * @extends DBElementRepository + */ class AttachmentRepository extends DBElementRepository { /** diff --git a/src/Repository/DBElementRepository.php b/src/Repository/DBElementRepository.php index 0f7024b6..fd410e3d 100644 --- a/src/Repository/DBElementRepository.php +++ b/src/Repository/DBElementRepository.php @@ -45,6 +45,10 @@ use App\Entity\Base\AbstractDBElement; use Doctrine\ORM\EntityRepository; use ReflectionClass; +/** + * @template TEntityClass of AbstractDBElement + * @extends EntityRepository + */ class DBElementRepository extends EntityRepository { /** @@ -52,12 +56,13 @@ class DBElementRepository extends EntityRepository * You should only use it to undelete former existing elements, everything else is most likely a bad idea! * * @param AbstractDBElement $element The element whose ID should be changed - * @param int $new_id The new ID + * @phpstan-param TEntityClass $element + * @param int $new_id The new ID */ public function changeID(AbstractDBElement $element, int $new_id): void { $qb = $this->createQueryBuilder('element'); - $q = $qb->update(get_class($element), 'element') + $q = $qb->update($element::class, 'element') ->set('element.id', $new_id) ->where('element.id = ?1') ->setParameter(1, $element->getID()) @@ -73,6 +78,7 @@ class DBElementRepository extends EntityRepository * Find all elements that match a list of IDs. * * @return AbstractDBElement[] + * @phpstan-return list */ public function getElementsFromIDArray(array $ids): array { @@ -87,7 +93,7 @@ class DBElementRepository extends EntityRepository protected function setField(AbstractDBElement $element, string $field, int $new_value): void { - $reflection = new ReflectionClass(get_class($element)); + $reflection = new ReflectionClass($element::class); $property = $reflection->getProperty($field); $property->setAccessible(true); $property->setValue($element, $new_value); diff --git a/src/Repository/LabelProfileRepository.php b/src/Repository/LabelProfileRepository.php index 76372f34..9fa5d3cc 100644 --- a/src/Repository/LabelProfileRepository.php +++ b/src/Repository/LabelProfileRepository.php @@ -43,21 +43,22 @@ namespace App\Repository; use App\Entity\LabelSystem\LabelOptions; use App\Entity\LabelSystem\LabelProfile; +use App\Entity\LabelSystem\LabelSupportedElement; use App\Helpers\Trees\TreeViewNode; use InvalidArgumentException; +/** + * @template TEntityClass of LabelProfile + * @extends NamedDBElementRepository + */ class LabelProfileRepository extends NamedDBElementRepository { /** * Find the profiles that are shown in the dropdown for the given type. * You should maybe use the cached version of this in LabelProfileDropdownHelper. */ - public function getDropdownProfiles(string $type): array + public function getDropdownProfiles(LabelSupportedElement $type): array { - if (!in_array($type, LabelOptions::SUPPORTED_ELEMENTS, true)) { - throw new InvalidArgumentException('Invalid supported_element type given.'); - } - return $this->findBy([ 'options.supported_element' => $type, 'show_in_dropdown' => true, @@ -74,7 +75,7 @@ class LabelProfileRepository extends NamedDBElementRepository { $result = []; - foreach (LabelOptions::SUPPORTED_ELEMENTS as $type) { + foreach (LabelSupportedElement::cases() as $type) { $type_children = []; $entities = $this->findForSupportedElement($type); foreach ($entities as $entity) { @@ -84,9 +85,9 @@ class LabelProfileRepository extends NamedDBElementRepository $type_children[] = $node; } - if (!empty($type_children)) { + if ($type_children !== []) { //Use default label e.g. 'part_label'. $$ marks that it will be translated in TreeViewGenerator - $tmp = new TreeViewNode('$$'.$type.'.label', null, $type_children); + $tmp = new TreeViewNode('$$'.$type->value.'.label', null, $type_children); $result[] = $tmp; } @@ -98,42 +99,35 @@ class LabelProfileRepository extends NamedDBElementRepository /** * Find all LabelProfiles that can be used with the given type. * - * @param string $type see LabelOptions::SUPPORTED_ELEMENTS for valid values + * @param LabelSupportedElement $type see LabelOptions::SUPPORTED_ELEMENTS for valid values * @param array $order_by The way the results should be sorted. By default ordered by */ - public function findForSupportedElement(string $type, array $order_by = ['name' => 'ASC']): array + public function findForSupportedElement(LabelSupportedElement $type, array $order_by = ['name' => 'ASC']): array { - if (!in_array($type, LabelOptions::SUPPORTED_ELEMENTS, true)) { - throw new InvalidArgumentException('Invalid supported_element type given.'); - } - return $this->findBy(['options.supported_element' => $type], $order_by); } /** * Returns all LabelProfiles that can be used for parts - * @return array */ public function getPartLabelProfiles(): array { - return $this->getDropdownProfiles('part'); + return $this->getDropdownProfiles(LabelSupportedElement::PART); } /** * Returns all LabelProfiles that can be used for part lots - * @return array */ public function getPartLotsLabelProfiles(): array { - return $this->getDropdownProfiles('part_lot'); + return $this->getDropdownProfiles(LabelSupportedElement::PART_LOT); } /** * Returns all LabelProfiles that can be used for storelocations - * @return array */ public function getStorelocationsLabelProfiles(): array { - return $this->getDropdownProfiles('storelocation'); + return $this->getDropdownProfiles(LabelSupportedElement::STORELOCATION); } } diff --git a/src/Repository/LogEntryRepository.php b/src/Repository/LogEntryRepository.php index 16ada41b..472993a7 100644 --- a/src/Repository/LogEntryRepository.php +++ b/src/Repository/LogEntryRepository.php @@ -28,10 +28,15 @@ use App\Entity\LogSystem\CollectionElementDeleted; use App\Entity\LogSystem\ElementCreatedLogEntry; use App\Entity\LogSystem\ElementDeletedLogEntry; use App\Entity\LogSystem\ElementEditedLogEntry; +use App\Entity\LogSystem\LogTargetType; use App\Entity\UserSystem\User; use DateTime; use RuntimeException; +/** + * @template TEntityClass of AbstractLogEntry + * @extends DBElementRepository + */ class LogEntryRepository extends DBElementRepository { public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array @@ -41,7 +46,7 @@ class LogEntryRepository extends DBElementRepository /** @var AbstractDBElement $element */ $element = $criteria['target']; $criteria['target_id'] = $element->getID(); - $criteria['target_type'] = AbstractLogEntry::targetTypeClassToID(get_class($element)); + $criteria['target_type'] = LogTargetType::fromElementClass($element); unset($criteria['target']); } @@ -52,15 +57,16 @@ class LogEntryRepository extends DBElementRepository * Find log entries associated with the given element (the history of the element). * * @param AbstractDBElement $element The element for which the history should be generated - * @param string $order By default, the newest entries are shown first. Change this to ASC to show the oldest entries first. - * @param null $limit - * @param null $offset + * @param string $order By default, the newest entries are shown first. Change this to ASC to show the oldest entries first. + * @param int|null $limit + * @param int|null $offset * * @return AbstractLogEntry[] */ - public function getElementHistory(AbstractDBElement $element, string $order = 'DESC', $limit = null, $offset = null): array + public function getElementHistory(AbstractDBElement $element, string $order = 'DESC', ?int $limit = null, ?int $offset = null): array { - return $this->findBy(['element' => $element], ['timestamp' => $order], $limit, $offset); + //@phpstan-ignore-next-line Target is parsed dynamically in findBy + return $this->findBy(['target' => $element], ['timestamp' => $order], $limit, $offset); } /** @@ -81,7 +87,7 @@ class LogEntryRepository extends DBElementRepository ->setMaxResults(1); $qb->setParameters([ - 'target_type' => AbstractLogEntry::targetTypeClassToID($class), + 'target_type' => LogTargetType::fromElementClass($class), 'target_id' => $id, ]); @@ -100,11 +106,11 @@ class LogEntryRepository extends DBElementRepository * Gets all log entries that are related to time travelling. * * @param AbstractDBElement $element The element for which the time travel data should be retrieved - * @param DateTime $until Back to which timestamp should the data be got (including the timestamp) + * @param \DateTimeInterface $until Back to which timestamp should the data be got (including the timestamp) * * @return AbstractLogEntry[] */ - public function getTimetravelDataForElement(AbstractDBElement $element, DateTime $until): array + public function getTimetravelDataForElement(AbstractDBElement $element, \DateTimeInterface $until): array { $qb = $this->createQueryBuilder('log'); $qb->select('log') @@ -117,7 +123,7 @@ class LogEntryRepository extends DBElementRepository ->orderBy('log.timestamp', 'DESC'); $qb->setParameters([ - 'target_type' => AbstractLogEntry::targetTypeClassToID(get_class($element)), + 'target_type' => LogTargetType::fromElementClass($element), 'target_id' => $element->getID(), 'until' => $until, ]); @@ -132,7 +138,7 @@ class LogEntryRepository extends DBElementRepository * * @return bool True if the element existed at the given timestamp */ - public function getElementExistedAtTimestamp(AbstractDBElement $element, DateTime $timestamp): bool + public function getElementExistedAtTimestamp(AbstractDBElement $element, \DateTimeInterface $timestamp): bool { $qb = $this->createQueryBuilder('log'); $qb->select('count(log)') @@ -143,7 +149,7 @@ class LogEntryRepository extends DBElementRepository ->orderBy('log.timestamp', 'DESC'); $qb->setParameters([ - 'target_type' => AbstractLogEntry::targetTypeClassToID(get_class($element)), + 'target_type' => LogTargetType::fromElementClass($element), 'target_id' => $element->getID(), 'until' => $timestamp, ]); @@ -151,15 +157,14 @@ class LogEntryRepository extends DBElementRepository $query = $qb->getQuery(); $count = $query->getSingleScalarResult(); - return !($count > 0); + return $count <= 0; } /** * Gets the last log entries ordered by timestamp. * - * @param string $order - * @param null $limit - * @param null $offset + * @param int|null $limit + * @param int|null $offset */ public function getLogsOrderedByTimestamp(string $order = 'DESC', $limit = null, $offset = null): array { @@ -205,18 +210,24 @@ class LogEntryRepository extends DBElementRepository return $this->getLastUser($element, ElementCreatedLogEntry::class); } - protected function getLastUser(AbstractDBElement $element, string $class): ?User + /** + * Returns the last user that has created a log entry with the given class on the given element. + * @param AbstractDBElement $element + * @param string $log_class + * @return User|null + */ + protected function getLastUser(AbstractDBElement $element, string $log_class): ?User { $qb = $this->createQueryBuilder('log'); $qb->select('log') //->where('log INSTANCE OF App\Entity\LogSystem\ElementEditedLogEntry') - ->where('log INSTANCE OF '.$class) + ->where('log INSTANCE OF '.$log_class) ->andWhere('log.target_type = :target_type') ->andWhere('log.target_id = :target_id') ->orderBy('log.timestamp', 'DESC'); $qb->setParameters([ - 'target_type' => AbstractLogEntry::targetTypeClassToID(get_class($element)), + 'target_type' => LogTargetType::fromElementClass($element), 'target_id' => $element->getID(), ]); diff --git a/src/Repository/NamedDBElementRepository.php b/src/Repository/NamedDBElementRepository.php index 8485d50a..772cc164 100644 --- a/src/Repository/NamedDBElementRepository.php +++ b/src/Repository/NamedDBElementRepository.php @@ -26,6 +26,10 @@ use App\Entity\Base\AbstractNamedDBElement; use App\Entity\UserSystem\User; use App\Helpers\Trees\TreeViewNode; +/** + * @template TEntityClass of AbstractNamedDBElement + * @extends DBElementRepository + */ class NamedDBElementRepository extends DBElementRepository { /** @@ -63,6 +67,7 @@ class NamedDBElementRepository extends DBElementRepository /** * Returns the list of all nodes to use in a select box. * @return AbstractNamedDBElement[] + * @phpstan-return array */ public function toNodesList(): array { diff --git a/src/Repository/ParameterRepository.php b/src/Repository/ParameterRepository.php index 37420306..a837435e 100644 --- a/src/Repository/ParameterRepository.php +++ b/src/Repository/ParameterRepository.php @@ -1,4 +1,7 @@ . */ - namespace App\Repository; +use App\Entity\Parameters\AbstractParameter; + +/** + * @template TEntityClass of AbstractParameter + * @extends DBElementRepository + */ class ParameterRepository extends DBElementRepository { /** * Find parameters using a parameter name * @param string $name The name to search for * @param bool $exact True, if only exact names should match. False, if the name just needs to be contained in the parameter name - * @param int $max_results - * @return array + * @phpstan-return array */ public function autocompleteParamName(string $name, bool $exact = false, int $max_results = 50): array { @@ -48,4 +55,4 @@ class ParameterRepository extends DBElementRepository return $qb->getQuery()->getArrayResult(); } -} \ No newline at end of file +} diff --git a/src/Repository/PartRepository.php b/src/Repository/PartRepository.php index fff8461a..3ab3ed31 100644 --- a/src/Repository/PartRepository.php +++ b/src/Repository/PartRepository.php @@ -22,11 +22,15 @@ declare(strict_types=1); namespace App\Repository; +use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; use Doctrine\ORM\NonUniqueResultException; use Doctrine\ORM\NoResultException; use Doctrine\ORM\QueryBuilder; +/** + * @extends NamedDBElementRepository + */ class PartRepository extends NamedDBElementRepository { /** @@ -67,6 +71,9 @@ class PartRepository extends NamedDBElementRepository return (int) ($query->getSingleScalarResult() ?? 0); } + /** + * @return Part[] + */ public function autocompleteSearch(string $query, int $max_limits = 50): array { $qb = $this->createQueryBuilder('part'); diff --git a/src/Repository/Parts/CategoryRepository.php b/src/Repository/Parts/CategoryRepository.php index c472d8d6..8e270047 100644 --- a/src/Repository/Parts/CategoryRepository.php +++ b/src/Repository/Parts/CategoryRepository.php @@ -1,4 +1,7 @@ . */ - namespace App\Repository\Parts; use App\Entity\Parts\Category; diff --git a/src/Repository/Parts/DeviceRepository.php b/src/Repository/Parts/DeviceRepository.php index e7555f2b..442c91e5 100644 --- a/src/Repository/Parts/DeviceRepository.php +++ b/src/Repository/Parts/DeviceRepository.php @@ -1,4 +1,7 @@ . */ - namespace App\Repository\Parts; @@ -49,4 +51,4 @@ class DeviceRepository extends StructuralDBElementRepository //Prevent user from deleting devices, to not accidentally remove filled devices from old versions return 1; } -} \ No newline at end of file +} diff --git a/src/Repository/Parts/FootprintRepository.php b/src/Repository/Parts/FootprintRepository.php index 72c25003..355cb1bb 100644 --- a/src/Repository/Parts/FootprintRepository.php +++ b/src/Repository/Parts/FootprintRepository.php @@ -1,4 +1,7 @@ . */ - namespace App\Repository\Parts; use App\Entity\Parts\Footprint; diff --git a/src/Repository/Parts/ManufacturerRepository.php b/src/Repository/Parts/ManufacturerRepository.php index aa4d8fec..a47142d4 100644 --- a/src/Repository/Parts/ManufacturerRepository.php +++ b/src/Repository/Parts/ManufacturerRepository.php @@ -1,4 +1,7 @@ . */ - namespace App\Repository\Parts; use App\Entity\Parts\Manufacturer; diff --git a/src/Repository/Parts/MeasurementUnitRepository.php b/src/Repository/Parts/MeasurementUnitRepository.php index 80d32743..1c9b106b 100644 --- a/src/Repository/Parts/MeasurementUnitRepository.php +++ b/src/Repository/Parts/MeasurementUnitRepository.php @@ -1,4 +1,7 @@ . */ - namespace App\Repository\Parts; use App\Entity\Parts\MeasurementUnit; diff --git a/src/Repository/Parts/StorelocationRepository.php b/src/Repository/Parts/StorelocationRepository.php index c0c432be..e6eea9a5 100644 --- a/src/Repository/Parts/StorelocationRepository.php +++ b/src/Repository/Parts/StorelocationRepository.php @@ -1,4 +1,7 @@ . */ - namespace App\Repository\Parts; use App\Entity\Parts\Part; diff --git a/src/Repository/Parts/SupplierRepository.php b/src/Repository/Parts/SupplierRepository.php index 0a2e2c8f..6dc995f1 100644 --- a/src/Repository/Parts/SupplierRepository.php +++ b/src/Repository/Parts/SupplierRepository.php @@ -1,4 +1,7 @@ . */ - namespace App\Repository\Parts; use App\Entity\Parts\Part; diff --git a/src/Repository/StructuralDBElementRepository.php b/src/Repository/StructuralDBElementRepository.php index 442c5860..c1882bda 100644 --- a/src/Repository/StructuralDBElementRepository.php +++ b/src/Repository/StructuralDBElementRepository.php @@ -27,6 +27,11 @@ use App\Helpers\Trees\StructuralDBElementIterator; use App\Helpers\Trees\TreeViewNode; use RecursiveIteratorIterator; +/** + * @see \App\Tests\Repository\StructuralDBElementRepositoryTest + * @template TEntityClass of AbstractStructuralDBElement + * @extends NamedDBElementRepository + */ class StructuralDBElementRepository extends NamedDBElementRepository { /** @@ -49,7 +54,8 @@ class StructuralDBElementRepository extends NamedDBElementRepository * Gets a tree of TreeViewNode elements. The root elements has $parent as parent. * The treeview is generic, that means the href are null and ID values are set. * - * @param AbstractStructuralDBElement|null $parent the parent the root elements should have + * @param AbstractStructuralDBElement|null $parent the parent the root elements should have + * @phpstan-param TEntityClass|null $parent * * @return TreeViewNode[] */ @@ -75,8 +81,9 @@ class StructuralDBElementRepository extends NamedDBElementRepository * Gets a flattened hierarchical tree. Useful for generating option lists. * * @param AbstractStructuralDBElement|null $parent This entity will be used as root element. Set to null, to use global root - * + * @phpstan-param TEntityClass|null $parent * @return AbstractStructuralDBElement[] a flattened list containing the tree elements + * @phpstan-return array */ public function toNodesList(?AbstractStructuralDBElement $parent = null): array { @@ -100,9 +107,8 @@ class StructuralDBElementRepository extends NamedDBElementRepository * Creates a structure of AbstractStructuralDBElements from a path separated by $separator, which splits the various levels. * This function will try to use existing elements, if they are already in the database. If not, they will be created. * An array of the created elements will be returned, with the last element being the deepest element. - * @param string $path - * @param string $separator * @return AbstractStructuralDBElement[] + * @phpstan-return array */ public function getNewEntityFromPath(string $path, string $separator = '->'): array { @@ -118,7 +124,7 @@ class StructuralDBElementRepository extends NamedDBElementRepository $entity = $this->getNewEntityFromCache($name, $parent); //See if we already have an element with this name and parent in the database - if (!$entity) { + if (!$entity instanceof AbstractStructuralDBElement) { $entity = $this->findOneBy(['name' => $name, 'parent' => $parent]); } if (null === $entity) { @@ -140,11 +146,8 @@ class StructuralDBElementRepository extends NamedDBElementRepository private function getNewEntityFromCache(string $name, ?AbstractStructuralDBElement $parent): ?AbstractStructuralDBElement { - $key = $parent ? $parent->getFullPath('%->%').'%->%'.$name : $name; - if (isset($this->new_entity_cache[$key])) { - return $this->new_entity_cache[$key]; - } - return null; + $key = $parent instanceof AbstractStructuralDBElement ? $parent->getFullPath('%->%').'%->%'.$name : $name; + return $this->new_entity_cache[$key] ?? null; } private function setNewEntityToCache(AbstractStructuralDBElement $entity): void @@ -157,9 +160,8 @@ class StructuralDBElementRepository extends NamedDBElementRepository * Returns an element of AbstractStructuralDBElements queried from a path separated by $separator, which splits the various levels. * An array of the created elements will be returned, with the last element being the deepest element. * If no element was found, an empty array will be returned. - * @param string $path - * @param string $separator * @return AbstractStructuralDBElement[] + * @phpstan-return array */ public function getEntityByPath(string $path, string $separator = '->'): array { diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 3cc29788..f1e738d0 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -24,6 +24,7 @@ namespace App\Repository; use App\Entity\UserSystem\User; use Doctrine\ORM\NonUniqueResultException; +use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; use Symfony\Component\Security\Core\User\UserInterface; @@ -32,6 +33,7 @@ use Symfony\Component\Security\Core\User\UserInterface; * @method User|null findOneBy(array $criteria, array $orderBy = null) * @method User[] findAll() * @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + * @extends NamedDBElementRepository */ final class UserRepository extends NamedDBElementRepository implements PasswordUpgraderInterface { @@ -43,7 +45,7 @@ final class UserRepository extends NamedDBElementRepository implements PasswordU */ public function getAnonymousUser(): ?User { - if (null === $this->anonymous_user) { + if (!$this->anonymous_user instanceof User) { $this->anonymous_user = $this->findOneBy([ 'id' => User::ID_ANONYMOUS, ]); @@ -61,7 +63,7 @@ final class UserRepository extends NamedDBElementRepository implements PasswordU */ public function findByEmailOrName(string $name_or_password): ?User { - if (empty($name_or_password)) { + if ($name_or_password === '') { return null; } @@ -77,12 +79,12 @@ final class UserRepository extends NamedDBElementRepository implements PasswordU try { return $qb->getQuery()->getOneOrNullResult(); - } catch (NonUniqueResultException $nonUniqueResultException) { + } catch (NonUniqueResultException) { return null; } } - public function upgradePassword(UserInterface $user, string $newHashedPassword): void + public function upgradePassword(UserInterface|PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void { if ($user instanceof User) { $user->setPassword($newHashedPassword); diff --git a/src/Security/EnsureSAMLUserForSAMLLoginChecker.php b/src/Security/EnsureSAMLUserForSAMLLoginChecker.php index bba13c01..7b540984 100644 --- a/src/Security/EnsureSAMLUserForSAMLLoginChecker.php +++ b/src/Security/EnsureSAMLUserForSAMLLoginChecker.php @@ -1,4 +1,7 @@ . */ - namespace App\Security; use App\Entity\UserSystem\User; -use Hslavich\OneloginSamlBundle\Security\Http\Authenticator\Token\SamlToken; +use Nbgrp\OneloginSamlBundle\Security\Http\Authenticator\Token\SamlToken; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; -use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; use Symfony\Contracts\Translation\TranslatorInterface; +/** + * @see \App\Tests\Security\EnsureSAMLUserForSAMLLoginCheckerTest + */ class EnsureSAMLUserForSAMLLoginChecker implements EventSubscriberInterface { - private TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator) + public function __construct(private readonly TranslatorInterface $translator) { - $this->translator = $translator; } public static function getSubscribedEvents(): array @@ -51,13 +52,12 @@ class EnsureSAMLUserForSAMLLoginChecker implements EventSubscriberInterface //If we are using SAML, we need to check that the user is a SAML user. if ($token instanceof SamlToken) { - if ($user instanceof User && !$user->isSAMLUser()) { + if ($user instanceof User && !$user->isSamlUser()) { throw new CustomUserMessageAccountStatusException($this->translator->trans('saml.error.cannot_login_local_user_per_saml', [], 'security')); } - } else { //Ensure that you can not login locally with a SAML user (even if this should not happen, as the password is not set) - if ($user instanceof User && $user->isSamlUser()) { - throw new CustomUserMessageAccountStatusException($this->translator->trans('saml.error.cannot_login_saml_user_locally', [], 'security')); - } + } elseif ($user instanceof User && $user->isSamlUser()) { + //Ensure that you can not login locally with a SAML user (even if this should not happen, as the password is not set) + throw new CustomUserMessageAccountStatusException($this->translator->trans('saml.error.cannot_login_saml_user_locally', [], 'security')); } } -} \ No newline at end of file +} diff --git a/src/Security/SamlUserFactory.php b/src/Security/SamlUserFactory.php index 39e67c0c..d5c68146 100644 --- a/src/Security/SamlUserFactory.php +++ b/src/Security/SamlUserFactory.php @@ -1,4 +1,7 @@ . */ - namespace App\Security; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use Doctrine\ORM\EntityManagerInterface; -use Hslavich\OneloginSamlBundle\Security\Http\Authenticator\Token\SamlToken; -use Hslavich\OneloginSamlBundle\Security\User\SamlUserFactoryInterface; +use Nbgrp\OneloginSamlBundle\Security\Http\Authenticator\Token\SamlToken; +use Nbgrp\OneloginSamlBundle\Security\User\SamlUserFactoryInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent; use Symfony\Component\Security\Core\User\UserInterface; +/** + * @see \App\Tests\Security\SamlUserFactoryTest + */ class SamlUserFactory implements SamlUserFactoryInterface, EventSubscriberInterface { - private EntityManagerInterface $em; - private array $saml_role_mapping; - private bool $update_group_on_login; + private readonly array $saml_role_mapping; - public function __construct(EntityManagerInterface $entityManager, ?array $saml_role_mapping, bool $update_group_on_login) + public function __construct(private readonly EntityManagerInterface $em, ?array $saml_role_mapping, private readonly bool $update_group_on_login) { - $this->em = $entityManager; - if ($saml_role_mapping) { - $this->saml_role_mapping = $saml_role_mapping; - } else { - $this->saml_role_mapping = []; - } - $this->update_group_on_login = $update_group_on_login; + $this->saml_role_mapping = $saml_role_mapping ?: []; } - public const SAML_PASSWORD_PLACEHOLDER = '!!SAML!!'; + final public const SAML_PASSWORD_PLACEHOLDER = '!!SAML!!'; public function createUser($username, array $attributes = []): UserInterface { @@ -70,8 +67,6 @@ class SamlUserFactory implements SamlUserFactoryInterface, EventSubscriberInterf /** * This method is called after a successful authentication. It is used to update the group of the user, * based on the new SAML attributes. - * @param AuthenticationSuccessEvent $event - * @return void */ public function onAuthenticationSuccess(AuthenticationSuccessEvent $event): void { @@ -98,7 +93,6 @@ class SamlUserFactory implements SamlUserFactoryInterface, EventSubscriberInterf /** * Maps the given SAML attributes to a local group. * @param array $attributes The SAML attributes - * @return Group|null */ public function mapSAMLAttributesToLocalGroup(array $attributes): ?Group { @@ -109,7 +103,7 @@ class SamlUserFactory implements SamlUserFactoryInterface, EventSubscriberInterf //Check if we can find a group with the given ID if ($group_id !== null) { $group = $this->em->find(Group::class, $group_id); - if ($group !== null) { + if ($group instanceof Group) { return $group; } } @@ -127,7 +121,7 @@ class SamlUserFactory implements SamlUserFactoryInterface, EventSubscriberInterf */ public function mapSAMLRolesToLocalGroupID(array $roles, array $map = null): ?int { - $map = $map ?? $this->saml_role_mapping; + $map ??= $this->saml_role_mapping; //Iterate over the mapping (from first to last) and check if we have a match foreach ($map as $saml_role => $group_id) { @@ -156,4 +150,4 @@ class SamlUserFactory implements SamlUserFactoryInterface, EventSubscriberInterf AuthenticationSuccessEvent::class => 'onAuthenticationSuccess', ]; } -} \ No newline at end of file +} diff --git a/src/Security/UserChecker.php b/src/Security/UserChecker.php index f64fb50e..fd53a295 100644 --- a/src/Security/UserChecker.php +++ b/src/Security/UserChecker.php @@ -29,13 +29,13 @@ use Symfony\Component\Security\Core\User\UserCheckerInterface; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Contracts\Translation\TranslatorInterface; +/** + * @see \App\Tests\Security\UserCheckerTest + */ final class UserChecker implements UserCheckerInterface { - private TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator) + public function __construct(private readonly TranslatorInterface $translator) { - $this->translator = $translator; } /** diff --git a/src/Security/Voter/AttachmentVoter.php b/src/Security/Voter/AttachmentVoter.php index 987787e0..0b32188c 100644 --- a/src/Security/Voter/AttachmentVoter.php +++ b/src/Security/Voter/AttachmentVoter.php @@ -22,6 +22,8 @@ declare(strict_types=1); namespace App\Security\Voter; +use Symfony\Bundle\SecurityBundle\Security; +use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentTypeAttachment; use App\Entity\Attachments\CategoryAttachment; @@ -39,18 +41,14 @@ use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; use Doctrine\ORM\EntityManagerInterface; use RuntimeException; -use Symfony\Component\Security\Core\Security; use function in_array; class AttachmentVoter extends ExtendedVoter { - protected Security $security; - - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, Security $security) + public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) { parent::__construct($resolver, $entityManager); - $this->security = $security; } /** @@ -76,7 +74,7 @@ class AttachmentVoter extends ExtendedVoter if (is_object($subject)) { //If the attachment has no element (which should not happen), we deny access, as we can not determine if the user is allowed to access the associated element $target_element = $subject->getElement(); - if ($target_element) { + if ($target_element instanceof AttachmentContainingDBElement) { return $this->security->isGranted($this->mapOperation($attribute), $target_element); } } @@ -112,7 +110,7 @@ class AttachmentVoter extends ExtendedVoter $param = 'parts'; } else { - throw new RuntimeException('Encountered unknown Parameter type: ' . (is_object($subject) ? get_class($subject) : $subject)); + throw new RuntimeException('Encountered unknown Parameter type: ' . $subject); } return $this->resolver->inherit($user, $param, $this->mapOperation($attribute)) ?? false; @@ -123,21 +121,12 @@ class AttachmentVoter extends ExtendedVoter private function mapOperation(string $attribute): string { - switch ($attribute) { - //We can view the attachment if we can view the element - case 'read': - case 'view': - return 'read'; - //We can edit/create/delete the attachment if we can edit the element - case 'edit': - case 'create': - case 'delete': - return 'edit'; - case 'show_history': - return 'show_history'; - } - - throw new \RuntimeException('Encountered unknown attribute "'.$attribute.'" in AttachmentVoter!'); + return match ($attribute) { + 'read', 'view' => 'read', + 'edit', 'create', 'delete' => 'edit', + 'show_history' => 'show_history', + default => throw new \RuntimeException('Encountered unknown attribute "'.$attribute.'" in AttachmentVoter!'), + }; } /** diff --git a/src/Security/Voter/ExtendedVoter.php b/src/Security/Voter/ExtendedVoter.php index 825d768c..279944fc 100644 --- a/src/Security/Voter/ExtendedVoter.php +++ b/src/Security/Voter/ExtendedVoter.php @@ -34,13 +34,8 @@ use Symfony\Component\Security\Core\Authorization\Voter\Voter; */ abstract class ExtendedVoter extends Voter { - protected EntityManagerInterface $entityManager; - protected PermissionManager $resolver; - - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager) + public function __construct(protected PermissionManager $resolver, protected EntityManagerInterface $entityManager) { - $this->resolver = $resolver; - $this->entityManager = $entityManager; } final protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool @@ -57,7 +52,7 @@ abstract class ExtendedVoter extends Voter /** @var UserRepository $repo */ $repo = $this->entityManager->getRepository(User::class); $user = $repo->getAnonymousUser(); - if (null === $user) { + if (!$user instanceof User) { return false; } } @@ -68,8 +63,6 @@ abstract class ExtendedVoter extends Voter /** * Similar to voteOnAttribute, but checking for the anonymous user is already done. * The current user (or the anonymous user) is passed by $user. - * - * @param string $attribute */ abstract protected function voteOnUser(string $attribute, $subject, User $user): bool; } diff --git a/src/Security/Voter/LogEntryVoter.php b/src/Security/Voter/LogEntryVoter.php index f05de596..20c34863 100644 --- a/src/Security/Voter/LogEntryVoter.php +++ b/src/Security/Voter/LogEntryVoter.php @@ -22,22 +22,19 @@ declare(strict_types=1); namespace App\Security\Voter; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\LogSystem\AbstractLogEntry; use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\Security\Core\Security; class LogEntryVoter extends ExtendedVoter { - public const ALLOWED_OPS = ['read', 'show_details', 'delete']; + final public const ALLOWED_OPS = ['read', 'show_details', 'delete']; - private Security $security; - - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, Security $security) + public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, private readonly Security $security) { parent::__construct($resolver, $entityManager); - $this->security = $security; } protected function voteOnUser(string $attribute, $subject, User $user): bool @@ -66,7 +63,7 @@ class LogEntryVoter extends ExtendedVoter //To view details of a element related log entry, the user needs to be able to view the history of this entity type $targetClass = $subject->getTargetClass(); if (null !== $targetClass) { - return $this->security->isGranted('show_history', $targetClass) ?? false; + return $this->security->isGranted('show_history', $targetClass); } //In other cases, this behaves like the read permission diff --git a/src/Security/Voter/OrderdetailVoter.php b/src/Security/Voter/OrderdetailVoter.php index eaeea11d..96ff609e 100644 --- a/src/Security/Voter/OrderdetailVoter.php +++ b/src/Security/Voter/OrderdetailVoter.php @@ -41,20 +41,18 @@ declare(strict_types=1); namespace App\Security\Voter; +use Symfony\Bundle\SecurityBundle\Security; +use App\Entity\Parts\Part; use App\Entity\PriceInformations\Orderdetail; use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\Security\Core\Security; class OrderdetailVoter extends ExtendedVoter { - protected Security $security; - - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, Security $security) + public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) { parent::__construct($resolver, $entityManager); - $this->security = $security; } protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element']; @@ -65,27 +63,16 @@ class OrderdetailVoter extends ExtendedVoter throw new \RuntimeException('This voter can only handle Orderdetail objects!'); } - switch ($attribute) { - case 'read': - $operation = 'read'; - break; - case 'edit': //As long as we can edit, we can also edit orderdetails - case 'create': - case 'delete': - $operation = 'edit'; - break; - case 'show_history': - $operation = 'show_history'; - break; - case 'revert_element': - $operation = 'revert_element'; - break; - default: - throw new \RuntimeException('Encountered unknown operation "'.$attribute.'"!'); - } + $operation = match ($attribute) { + 'read' => 'read', + 'edit', 'create', 'delete' => 'edit', + 'show_history' => 'show_history', + 'revert_element' => 'revert_element', + default => throw new \RuntimeException('Encountered unknown operation "'.$attribute.'"!'), + }; //If we have no part associated use the generic part permission - if (is_string($subject) || $subject->getPart() === null) { + if (is_string($subject) || !$subject->getPart() instanceof Part) { return $this->resolver->inherit($user, 'parts', $operation) ?? false; } diff --git a/src/Security/Voter/ParameterVoter.php b/src/Security/Voter/ParameterVoter.php index b15afe46..6f752518 100644 --- a/src/Security/Voter/ParameterVoter.php +++ b/src/Security/Voter/ParameterVoter.php @@ -1,4 +1,7 @@ . */ - namespace App\Security\Voter; +use Symfony\Bundle\SecurityBundle\Security; +use App\Entity\Base\AbstractDBElement; use App\Entity\Parameters\AbstractParameter; use App\Entity\Parameters\AttachmentTypeParameter; use App\Entity\Parameters\CategoryParameter; @@ -36,16 +40,12 @@ use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; use Doctrine\ORM\EntityManagerInterface; use RuntimeException; -use Symfony\Component\Security\Core\Security; class ParameterVoter extends ExtendedVoter { - protected Security $security; - - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, Security $security) + public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) { - $this->security = $security; parent::__construct($resolver, $entityManager); } @@ -60,31 +60,14 @@ class ParameterVoter extends ExtendedVoter if (is_object($subject)) { //If the attachment has no element (which should not happen), we deny access, as we can not determine if the user is allowed to access the associated element $target_element = $subject->getElement(); - if ($target_element !== null) { - //Depending on the operation delegate either to the attachments element or to the attachment permission - - - switch ($attribute) { - //We can view the attachment if we can view the element - case 'read': - case 'view': - $operation = 'read'; - break; - //We can edit/create/delete the attachment if we can edit the element - case 'edit': - case 'create': - case 'delete': - $operation = 'edit'; - break; - case 'show_history': - $operation = 'show_history'; - break; - case 'revert_element': - $operation = 'revert_element'; - break; - default: - throw new RuntimeException('Unknown operation: '.$attribute); - } + if ($target_element instanceof AbstractDBElement) { + $operation = match ($attribute) { + 'read', 'view' => 'read', + 'edit', 'create', 'delete' => 'edit', + 'show_history' => 'show_history', + 'revert_element' => 'revert_element', + default => throw new RuntimeException('Unknown operation: '.$attribute), + }; return $this->security->isGranted($operation, $target_element); } @@ -118,7 +101,7 @@ class ParameterVoter extends ExtendedVoter $param = 'parts'; } else { - throw new RuntimeException('Encountered unknown Parameter type: ' . (is_object($subject) ? get_class($subject) : $subject)); + throw new RuntimeException('Encountered unknown Parameter type: ' . (is_object($subject) ? $subject::class : $subject)); } return $this->resolver->inherit($user, $param, $attribute) ?? false; @@ -134,4 +117,4 @@ class ParameterVoter extends ExtendedVoter //Allow class name as subject return false; } -} \ No newline at end of file +} diff --git a/src/Security/Voter/PartLotVoter.php b/src/Security/Voter/PartLotVoter.php index 0c70e629..c9ddb89e 100644 --- a/src/Security/Voter/PartLotVoter.php +++ b/src/Security/Voter/PartLotVoter.php @@ -41,20 +41,18 @@ declare(strict_types=1); namespace App\Security\Voter; +use Symfony\Bundle\SecurityBundle\Security; +use App\Entity\Parts\Part; use App\Entity\Parts\PartLot; use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\Security\Core\Security; class PartLotVoter extends ExtendedVoter { - protected Security $security; - - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, Security $security) + public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) { parent::__construct($resolver, $entityManager); - $this->security = $security; } protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element', 'withdraw', 'add', 'move']; @@ -65,7 +63,7 @@ class PartLotVoter extends ExtendedVoter throw new \RuntimeException('This voter can only handle PartLot objects!'); } - if (in_array($attribute, ['withdraw', 'add', 'move'])) + if (in_array($attribute, ['withdraw', 'add', 'move'], true)) { $base_permission = $this->resolver->inherit($user, 'parts_stock', $attribute) ?? false; @@ -78,27 +76,16 @@ class PartLotVoter extends ExtendedVoter return $base_permission && $lot_permission; } - switch ($attribute) { - case 'read': - $operation = 'read'; - break; - case 'edit': //As long as we can edit, we can also edit orderdetails - case 'create': - case 'delete': - $operation = 'edit'; - break; - case 'show_history': - $operation = 'show_history'; - break; - case 'revert_element': - $operation = 'revert_element'; - break; - default: - throw new \RuntimeException('Encountered unknown operation "'.$attribute.'"!'); - } + $operation = match ($attribute) { + 'read' => 'read', + 'edit', 'create', 'delete' => 'edit', + 'show_history' => 'show_history', + 'revert_element' => 'revert_element', + default => throw new \RuntimeException('Encountered unknown operation "'.$attribute.'"!'), + }; //If we have no part associated use the generic part permission - if (is_string($subject) || $subject->getPart() === null) { + if (is_string($subject) || !$subject->getPart() instanceof Part) { return $this->resolver->inherit($user, 'parts', $operation) ?? false; } diff --git a/src/Security/Voter/PartVoter.php b/src/Security/Voter/PartVoter.php index fb1e3a38..878fc6a4 100644 --- a/src/Security/Voter/PartVoter.php +++ b/src/Security/Voter/PartVoter.php @@ -32,7 +32,7 @@ use App\Entity\UserSystem\User; */ class PartVoter extends ExtendedVoter { - public const READ = 'read'; + final public const READ = 'read'; protected function supports($attribute, $subject): bool { diff --git a/src/Security/Voter/PricedetailVoter.php b/src/Security/Voter/PricedetailVoter.php index eb4a81aa..c4def709 100644 --- a/src/Security/Voter/PricedetailVoter.php +++ b/src/Security/Voter/PricedetailVoter.php @@ -41,20 +41,19 @@ declare(strict_types=1); namespace App\Security\Voter; +use Symfony\Bundle\SecurityBundle\Security; +use App\Entity\PriceInformations\Orderdetail; +use App\Entity\Parts\Part; use App\Entity\PriceInformations\Pricedetail; use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\Security\Core\Security; class PricedetailVoter extends ExtendedVoter { - protected Security $security; - - public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, Security $security) + public function __construct(PermissionManager $resolver, EntityManagerInterface $entityManager, protected Security $security) { parent::__construct($resolver, $entityManager); - $this->security = $security; } protected const ALLOWED_PERMS = ['read', 'edit', 'create', 'delete', 'show_history', 'revert_element']; @@ -65,27 +64,16 @@ class PricedetailVoter extends ExtendedVoter throw new \RuntimeException('This voter can only handle Pricedetails objects!'); } - switch ($attribute) { - case 'read': - $operation = 'read'; - break; - case 'edit': //As long as we can edit, we can also edit orderdetails - case 'create': - case 'delete': - $operation = 'edit'; - break; - case 'show_history': - $operation = 'show_history'; - break; - case 'revert_element': - $operation = 'revert_element'; - break; - default: - throw new \RuntimeException('Encountered unknown operation "'.$attribute.'"!'); - } + $operation = match ($attribute) { + 'read' => 'read', + 'edit', 'create', 'delete' => 'edit', + 'show_history' => 'show_history', + 'revert_element' => 'revert_element', + default => throw new \RuntimeException('Encountered unknown operation "'.$attribute.'"!'), + }; //If we have no part associated use the generic part permission - if (is_string($subject) || $subject->getOrderdetail() === null || $subject->getOrderdetail()->getPart() === null) { + if (is_string($subject) || !$subject->getOrderdetail() instanceof Orderdetail || !$subject->getOrderdetail()->getPart() instanceof Part) { return $this->resolver->inherit($user, 'parts', $operation) ?? false; } diff --git a/src/Security/Voter/StructureVoter.php b/src/Security/Voter/StructureVoter.php index a37083ea..79cef811 100644 --- a/src/Security/Voter/StructureVoter.php +++ b/src/Security/Voter/StructureVoter.php @@ -77,11 +77,7 @@ class StructureVoter extends ExtendedVoter */ protected function instanceToPermissionName($subject): ?string { - if (!is_string($subject)) { - $class_name = get_class($subject); - } else { - $class_name = $subject; - } + $class_name = is_string($subject) ? $subject : $subject::class; //If it is existing in index, we can skip the loop if (isset(static::OBJ_PERM_MAP[$class_name])) { diff --git a/src/Security/Voter/UserVoter.php b/src/Security/Voter/UserVoter.php index 5e34b8cc..e98e1701 100644 --- a/src/Security/Voter/UserVoter.php +++ b/src/Security/Voter/UserVoter.php @@ -44,7 +44,7 @@ class UserVoter extends ExtendedVoter $this->resolver->listOperationsForPermission('self'), ['info'] ), - false + true ); } diff --git a/src/Serializer/BigNumberNormalizer.php b/src/Serializer/BigNumberNormalizer.php index 89826962..8bb686ee 100644 --- a/src/Serializer/BigNumberNormalizer.php +++ b/src/Serializer/BigNumberNormalizer.php @@ -1,4 +1,7 @@ . */ - namespace App\Serializer; use Brick\Math\BigNumber; -use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; -class BigNumberNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface +/** + * @see \App\Tests\Serializer\BigNumberNormalizerTest + */ +class BigNumberNormalizer implements NormalizerInterface { - public function supportsNormalization($data, string $format = null): bool + public function supportsNormalization($data, string $format = null, array $context = []): bool { return $data instanceof BigNumber; } @@ -41,8 +45,13 @@ class BigNumberNormalizer implements NormalizerInterface, CacheableSupportsMetho return (string) $object; } - public function hasCacheableSupportsMethod(): bool + /** + * @return bool[] + */ + public function getSupportedTypes(?string $format): array { - return true; + return [ + BigNumber::class => true, + ]; } -} \ No newline at end of file +} diff --git a/src/Serializer/PartNormalizer.php b/src/Serializer/PartNormalizer.php index ce910d8c..b453a58e 100644 --- a/src/Serializer/PartNormalizer.php +++ b/src/Serializer/PartNormalizer.php @@ -1,4 +1,7 @@ . */ - namespace App\Serializer; use App\Entity\Parts\Part; @@ -27,13 +29,20 @@ use App\Entity\Parts\Supplier; use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Pricedetail; use Brick\Math\BigDecimal; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -class PartNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface +/** + * @see \App\Tests\Serializer\PartNormalizerTest + */ +class PartNormalizer implements NormalizerInterface, DenormalizerInterface { + private const DENORMALIZE_KEY_MAPPING = [ 'notes' => 'comment', 'quantity' => 'instock', @@ -44,21 +53,27 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Cach 'storage_location' => 'storelocation', ]; - private ObjectNormalizer $normalizer; - private StructuralElementFromNameDenormalizer $locationDenormalizer; - - public function __construct(ObjectNormalizer $normalizer, StructuralElementFromNameDenormalizer $locationDenormalizer) + public function __construct( + private readonly StructuralElementFromNameDenormalizer $locationDenormalizer, + #[Autowire(service: ObjectNormalizer::class)] + private readonly NormalizerInterface $normalizer, + #[Autowire(service: ObjectNormalizer::class)] + private readonly DenormalizerInterface $denormalizer, + ) { - $this->normalizer = $normalizer; - $this->locationDenormalizer = $locationDenormalizer; } - public function supportsNormalization($data, string $format = null): bool + public function supportsNormalization($data, string $format = null, array $context = []): bool { return $data instanceof Part; } - public function normalize($object, string $format = null, array $context = []): array + /** + * @return (float|mixed)[]|\ArrayObject|null|scalar + * + * @psalm-return \ArrayObject|array{total_instock: float|mixed,...}|null|scalar + */ + public function normalize($object, string $format = null, array $context = []) { if (!$object instanceof Part) { throw new \InvalidArgumentException('This normalizer only supports Part objects!'); @@ -76,7 +91,7 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Cach return $data; } - public function supportsDenormalization($data, string $type, string $format = null): bool + public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { return is_array($data) && is_a($type, Part::class, true); } @@ -114,18 +129,18 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Cach $data['minamount'] = 0.0; } - $object = $this->normalizer->denormalize($data, $type, $format, $context); + $object = $this->denormalizer->denormalize($data, $type, $format, $context); if (!$object instanceof Part) { throw new \InvalidArgumentException('This normalizer only supports Part objects!'); } - if ((isset($data['instock']) && trim($data['instock']) !== "") || (isset($data['storelocation']) && trim($data['storelocation']) !== "")) { + if ((isset($data['instock']) && trim((string) $data['instock']) !== "") || (isset($data['storelocation']) && trim((string) $data['storelocation']) !== "")) { $partLot = new PartLot(); if (isset($data['instock']) && $data['instock'] !== "") { //Replace comma with dot - $instock = (float) str_replace(',', '.', $data['instock']); + $instock = (float) str_replace(',', '.', (string) $data['instock']); $partLot->setAmount($instock); } else { @@ -157,7 +172,7 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Cach $pricedetail = new Pricedetail(); $pricedetail->setMinDiscountQuantity(1); $pricedetail->setPriceRelatedQuantity(1); - $price = BigDecimal::of(str_replace(',', '.', $data['price'])); + $price = BigDecimal::of(str_replace(',', '.', (string) $data['price'])); $pricedetail->setPrice($price); $orderdetail->addPricedetail($pricedetail); @@ -168,9 +183,14 @@ class PartNormalizer implements NormalizerInterface, DenormalizerInterface, Cach return $object; } - public function hasCacheableSupportsMethod(): bool + /** + * @return bool[] + */ + public function getSupportedTypes(?string $format): array { //Must be false, because we rely on is_array($data) in supportsDenormalization() - return false; + return [ + Part::class => false, + ]; } -} \ No newline at end of file +} diff --git a/src/Serializer/StructuralElementDenormalizer.php b/src/Serializer/StructuralElementDenormalizer.php index c4cfb09d..ce6f91ca 100644 --- a/src/Serializer/StructuralElementDenormalizer.php +++ b/src/Serializer/StructuralElementDenormalizer.php @@ -1,4 +1,7 @@ . */ - namespace App\Serializer; use App\Entity\Base\AbstractStructuralDBElement; use App\Repository\StructuralDBElementRepository; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; -use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -class StructuralElementDenormalizer implements ContextAwareDenormalizerInterface, CacheableSupportsMethodInterface +/** + * @see \App\Tests\Serializer\StructuralElementDenormalizerTest + */ +class StructuralElementDenormalizer implements DenormalizerInterface { - private DenormalizerInterface $normalizer; - private EntityManagerInterface $entityManager; - private array $object_cache = []; - public function __construct(ObjectNormalizer $normalizer, EntityManagerInterface $entityManager) + public function __construct( + private readonly EntityManagerInterface $entityManager, + #[Autowire(service: ObjectNormalizer::class)] + private readonly DenormalizerInterface $denormalizer) { - $this->normalizer = $normalizer; - $this->entityManager = $entityManager; } public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool @@ -53,7 +58,7 @@ class StructuralElementDenormalizer implements ContextAwareDenormalizerInterface public function denormalize($data, string $type, string $format = null, array $context = []): ?AbstractStructuralDBElement { /** @var AbstractStructuralDBElement $deserialized_entity */ - $deserialized_entity = $this->normalizer->denormalize($data, $type, $format, $context); + $deserialized_entity = $this->denormalizer->denormalize($data, $type, $format, $context); //Check if we already have the entity in the database (via path) /** @var StructuralDBElementRepository $repo */ @@ -61,7 +66,7 @@ class StructuralElementDenormalizer implements ContextAwareDenormalizerInterface $path = $deserialized_entity->getFullPath(AbstractStructuralDBElement::PATH_DELIMITER_ARROW); $db_elements = $repo->getEntityByPath($path, AbstractStructuralDBElement::PATH_DELIMITER_ARROW); - if ($db_elements) { + if ($db_elements !== []) { //We already have the entity in the database, so we can return it return end($db_elements); } @@ -84,8 +89,11 @@ class StructuralElementDenormalizer implements ContextAwareDenormalizerInterface return $deserialized_entity; } - public function hasCacheableSupportsMethod(): bool + public function getSupportedTypes(): array { - return false; + //Must be false, because we use in_array in supportsDenormalization + return [ + AbstractStructuralDBElement::class => false, + ]; } -} \ No newline at end of file +} diff --git a/src/Serializer/StructuralElementFromNameDenormalizer.php b/src/Serializer/StructuralElementFromNameDenormalizer.php index 8516c951..4277fed4 100644 --- a/src/Serializer/StructuralElementFromNameDenormalizer.php +++ b/src/Serializer/StructuralElementFromNameDenormalizer.php @@ -1,4 +1,7 @@ . */ - namespace App\Serializer; use App\Entity\Base\AbstractStructuralDBElement; @@ -26,21 +28,26 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; -class StructuralElementFromNameDenormalizer implements DenormalizerInterface, CacheableSupportsMethodInterface +/** + * @see \App\Tests\Serializer\StructuralElementFromNameDenormalizerTest + */ +class StructuralElementFromNameDenormalizer implements DenormalizerInterface { - private EntityManagerInterface $em; - - public function __construct(EntityManagerInterface $em) + public function __construct(private readonly EntityManagerInterface $em) { - $this->em = $em; } - public function supportsDenormalization($data, string $type, string $format = null): bool + public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool { return is_string($data) && is_subclass_of($type, AbstractStructuralDBElement::class); } - public function denormalize($data, string $type, string $format = null, array $context = []): ?AbstractStructuralDBElement + /** + * @template T of AbstractStructuralDBElement + * @phpstan-param class-string $type + * @phpstan-return T|null + */ + public function denormalize($data, string $type, string $format = null, array $context = []): AbstractStructuralDBElement|null { //Retrieve the repository for the given type /** @var StructuralDBElementRepository $repo */ @@ -54,22 +61,27 @@ class StructuralElementFromNameDenormalizer implements DenormalizerInterface, Ca foreach ($elements as $element) { $this->em->persist($element); } - if (empty($elements)) { + if ($elements === []) { return null; } return end($elements); } $elements = $repo->getEntityByPath($data, $path_delimiter); - if (empty($elements)) { + if ($elements === []) { return null; } return end($elements); } - public function hasCacheableSupportsMethod(): bool + /** + * @return bool[] + */ + public function getSupportedTypes(?string $format): array { - //Must be false, because we do an is_string check on data in supportsDenormalization - return false; + //Cachable value Must be false, because we do an is_string check on data in supportsDenormalization + return [ + AbstractStructuralDBElement::class => false + ]; } -} \ No newline at end of file +} diff --git a/src/Serializer/StructuralElementNormalizer.php b/src/Serializer/StructuralElementNormalizer.php index 060fecaf..fc64aec0 100644 --- a/src/Serializer/StructuralElementNormalizer.php +++ b/src/Serializer/StructuralElementNormalizer.php @@ -1,4 +1,7 @@ . */ - namespace App\Serializer; use App\Entity\Base\AbstractStructuralDBElement; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface; +use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait; use Symfony\Component\Serializer\Normalizer\NormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -class StructuralElementNormalizer implements NormalizerInterface, CacheableSupportsMethodInterface +/** + * @see \App\Tests\Serializer\StructuralElementNormalizerTest + */ +class StructuralElementNormalizer implements NormalizerInterface { - private NormalizerInterface $normalizer; - - public function __construct(ObjectNormalizer $normalizer) + public function __construct( + #[Autowire(service: ObjectNormalizer::class)]private readonly NormalizerInterface $normalizer + ) { - $this->normalizer = $normalizer; } - public function supportsNormalization($data, string $format = null): bool + public function supportsNormalization($data, string $format = null, array $context = []): bool { return $data instanceof AbstractStructuralDBElement; } - public function normalize($object, string $format = null, array $context = []): array + /** + * @return array + */ + public function normalize($object, string $format = null, array $context = []) { if (!$object instanceof AbstractStructuralDBElement) { throw new \InvalidArgumentException('This normalizer only supports AbstractStructural objects!'); @@ -57,8 +67,13 @@ class StructuralElementNormalizer implements NormalizerInterface, CacheableSuppo return $data; } - public function hasCacheableSupportsMethod(): bool + /** + * @return bool[] + */ + public function getSupportedTypes(?string $format): array { - return true; + return [ + AbstractStructuralDBElement::class => true, + ]; } -} \ No newline at end of file +} diff --git a/src/Services/Attachments/AttachmentManager.php b/src/Services/Attachments/AttachmentManager.php index 81b1334f..4429179e 100644 --- a/src/Services/Attachments/AttachmentManager.php +++ b/src/Services/Attachments/AttachmentManager.php @@ -35,11 +35,8 @@ use function strlen; */ class AttachmentManager { - protected AttachmentPathResolver $pathResolver; - - public function __construct(AttachmentPathResolver $pathResolver) + public function __construct(protected AttachmentPathResolver $pathResolver) { - $this->pathResolver = $pathResolver; } /** @@ -67,7 +64,7 @@ class AttachmentManager */ public function toAbsoluteFilePath(Attachment $attachment): ?string { - if (empty($attachment->getPath())) { + if ($attachment->getPath() === '') { return null; } @@ -101,7 +98,7 @@ class AttachmentManager */ public function isFileExisting(Attachment $attachment): bool { - if (empty($attachment->getPath())) { + if ($attachment->getPath() === '') { return false; } diff --git a/src/Services/Attachments/AttachmentPathResolver.php b/src/Services/Attachments/AttachmentPathResolver.php index 9617024e..4e1a7149 100644 --- a/src/Services/Attachments/AttachmentPathResolver.php +++ b/src/Services/Attachments/AttachmentPathResolver.php @@ -29,17 +29,16 @@ use Symfony\Component\Filesystem\Filesystem; /** * This service converts the relative pathes for attachments saved in database (like %MEDIA%/img.jpg) to real pathes * an vice versa. + * @see \App\Tests\Services\Attachments\AttachmentPathResolverTest */ class AttachmentPathResolver { - protected string $project_dir; - - protected ?string $media_path; - protected ?string $footprints_path; + protected string $media_path; + protected string $footprints_path; protected ?string $models_path; protected ?string $secure_path; - protected array $placeholders; + protected array $placeholders = ['%MEDIA%', '%BASE%/data/media', '%FOOTPRINTS%', '%FOOTPRINTS_3D%', '%SECURE%']; protected array $pathes; protected array $placeholders_regex; protected array $pathes_regex; @@ -53,18 +52,13 @@ class AttachmentPathResolver * Set to null if this ressource should be disabled. * @param string|null $models_path set to null if this ressource should be disabled */ - public function __construct(string $project_dir, string $media_path, string $secure_path, ?string $footprints_path, ?string $models_path) + public function __construct(protected string $project_dir, string $media_path, string $secure_path, ?string $footprints_path, ?string $models_path) { - $this->project_dir = $project_dir; - - //Determine the path for our ressources - $this->media_path = $this->parameterToAbsolutePath($media_path); - $this->footprints_path = $this->parameterToAbsolutePath($footprints_path); + //Determine the path for our resources + $this->media_path = $this->parameterToAbsolutePath($media_path) ?? throw new \InvalidArgumentException('The media path must be set and valid!'); + $this->secure_path = $this->parameterToAbsolutePath($secure_path) ?? throw new \InvalidArgumentException('The secure path must be set and valid!'); + $this->footprints_path = $this->parameterToAbsolutePath($footprints_path) ; $this->models_path = $this->parameterToAbsolutePath($models_path); - $this->secure_path = $this->parameterToAbsolutePath($secure_path); - - //Here we define the valid placeholders and their replacement values - $this->placeholders = ['%MEDIA%', '%BASE%/data/media', '%FOOTPRINTS%', '%FOOTPRINTS_3D%', '%SECURE%']; $this->pathes = [$this->media_path, $this->media_path, $this->footprints_path, $this->models_path, $this->secure_path]; //Remove all disabled placeholders @@ -134,7 +128,7 @@ class AttachmentPathResolver $count = 0; //When path is a footprint we have to first run the string through our lecagy german mapping functions - if (strpos($placeholder_path, '%FOOTPRINTS%') !== false) { + if (str_contains($placeholder_path, '%FOOTPRINTS%')) { $placeholder_path = $this->convertOldFootprintPath($placeholder_path); } @@ -151,7 +145,7 @@ class AttachmentPathResolver } //Path is invalid if path is directory traversal - if (false !== strpos($placeholder_path, '..')) { + if (str_contains($placeholder_path, '..')) { return null; } @@ -198,7 +192,7 @@ class AttachmentPathResolver } /** - * The path where uploaded attachments is stored. + * The path where uploaded attachments is stored. * * @return string the absolute path to the media folder */ @@ -208,8 +202,8 @@ class AttachmentPathResolver } /** - * The path where secured attachments are stored. Must not be located in public/ folder, so it can only be accessed - * via the attachment controller. + * The path where secured attachments are stored. Must not be located in public/ folder, so it can only be accessed + * via the attachment controller. * * @return string the absolute path to the secure path */ @@ -221,7 +215,7 @@ class AttachmentPathResolver /** * The string where the builtin footprints are stored. * - * @return string|null The absolute path to the footprints folder. Null if built footprints were disabled. + * @return string|null The absolute path to the footprints' folder. Null if built footprints were disabled. */ public function getFootprintsPath(): ?string { @@ -231,7 +225,7 @@ class AttachmentPathResolver /** * The string where the builtin 3D models are stored. * - * @return string|null The absolute path to the models folder. Null if builtin models were disabled. + * @return string|null The absolute path to the models' folder. Null if builtin models were disabled. */ public function getModelsPath(): ?string { @@ -248,7 +242,7 @@ class AttachmentPathResolver $ret = []; foreach ($array as $item) { - $item = str_replace(['\\'], ['/'], $item); + $item = str_replace(['\\'], ['/'], (string) $item); $ret[] = '/'.preg_quote($item, '/').'/'; } diff --git a/src/Services/Attachments/AttachmentReverseSearch.php b/src/Services/Attachments/AttachmentReverseSearch.php index efcbb5bd..5f4f86de 100644 --- a/src/Services/Attachments/AttachmentReverseSearch.php +++ b/src/Services/Attachments/AttachmentReverseSearch.php @@ -34,18 +34,8 @@ use Symfony\Component\Filesystem\Filesystem; */ class AttachmentReverseSearch { - protected EntityManagerInterface $em; - protected AttachmentPathResolver $pathResolver; - protected CacheManager $cacheManager; - protected AttachmentURLGenerator $attachmentURLGenerator; - - public function __construct(EntityManagerInterface $em, AttachmentPathResolver $pathResolver, - CacheManager $cacheManager, AttachmentURLGenerator $attachmentURLGenerator) + public function __construct(protected EntityManagerInterface $em, protected AttachmentPathResolver $pathResolver, protected CacheManager $cacheManager, protected AttachmentURLGenerator $attachmentURLGenerator) { - $this->em = $em; - $this->pathResolver = $pathResolver; - $this->cacheManager = $cacheManager; - $this->attachmentURLGenerator = $attachmentURLGenerator; } /** diff --git a/src/Services/Attachments/AttachmentSubmitHandler.php b/src/Services/Attachments/AttachmentSubmitHandler.php index 4dca3c4c..b8b15907 100644 --- a/src/Services/Attachments/AttachmentSubmitHandler.php +++ b/src/Services/Attachments/AttachmentSubmitHandler.php @@ -55,16 +55,7 @@ use Symfony\Contracts\HttpClient\HttpClientInterface; */ class AttachmentSubmitHandler { - protected AttachmentPathResolver $pathResolver; protected array $folder_mapping; - protected bool $allow_attachments_downloads; - protected HttpClientInterface $httpClient; - protected MimeTypesInterface $mimeTypes; - protected FileTypeFilterTools $filterTools; - /** - * @var string The user configured maximum upload size. This is a string like "10M" or "1G" and will be converted to - */ - protected string $max_upload_size; private ?int $max_upload_size_bytes = null; @@ -72,18 +63,13 @@ class AttachmentSubmitHandler 'asp', 'cgi', 'py', 'pl', 'exe', 'aspx', 'js', 'mjs', 'jsp', 'css', 'jar', 'html', 'htm', 'shtm', 'shtml', 'htaccess', 'htpasswd', '']; - public function __construct(AttachmentPathResolver $pathResolver, bool $allow_attachments_downloads, - HttpClientInterface $httpClient, MimeTypesInterface $mimeTypes, - FileTypeFilterTools $filterTools, string $max_upload_size) + public function __construct(protected AttachmentPathResolver $pathResolver, protected bool $allow_attachments_downloads, + protected HttpClientInterface $httpClient, protected MimeTypesInterface $mimeTypes, + protected FileTypeFilterTools $filterTools, /** + * @var string The user configured maximum upload size. This is a string like "10M" or "1G" and will be converted to + */ + protected string $max_upload_size) { - $this->pathResolver = $pathResolver; - $this->allow_attachments_downloads = $allow_attachments_downloads; - $this->httpClient = $httpClient; - $this->mimeTypes = $mimeTypes; - $this->max_upload_size = $max_upload_size; - - $this->filterTools = $filterTools; - //The mapping used to determine which folder will be used for an attachment type $this->folder_mapping = [ PartAttachment::class => 'part', @@ -109,7 +95,7 @@ class AttachmentSubmitHandler public function isValidFileExtension(AttachmentType $attachment_type, UploadedFile $uploadedFile): bool { //Only validate if the attachment type has specified a filetype filter: - if (empty($attachment_type->getFiletypeFilter())) { + if ($attachment_type->getFiletypeFilter() === '') { return true; } @@ -155,25 +141,21 @@ class AttachmentSubmitHandler */ public function generateAttachmentPath(Attachment $attachment, bool $secure_upload = false): string { - if ($secure_upload) { - $base_path = $this->pathResolver->getSecurePath(); - } else { - $base_path = $this->pathResolver->getMediaPath(); - } + $base_path = $secure_upload ? $this->pathResolver->getSecurePath() : $this->pathResolver->getMediaPath(); //Ensure the given attachment class is known to mapping - if (!isset($this->folder_mapping[get_class($attachment)])) { - throw new InvalidArgumentException('The given attachment class is not known! The passed class was: '.get_class($attachment)); + if (!isset($this->folder_mapping[$attachment::class])) { + throw new InvalidArgumentException('The given attachment class is not known! The passed class was: '.$attachment::class); } //Ensure the attachment has an assigned element - if (null === $attachment->getElement()) { + if (!$attachment->getElement() instanceof AttachmentContainingDBElement) { throw new InvalidArgumentException('The given attachment is not assigned to an element! An element is needed to generate a path!'); } //Build path return $base_path.DIRECTORY_SEPARATOR //Base path - .$this->folder_mapping[get_class($attachment)].DIRECTORY_SEPARATOR.$attachment->getElement()->getID(); + .$this->folder_mapping[$attachment::class].DIRECTORY_SEPARATOR.$attachment->getElement()->getID(); } /** @@ -194,7 +176,7 @@ class AttachmentSubmitHandler $options = $resolver->resolve($options); //When a file is given then upload it, otherwise check if we need to download the URL - if ($file) { + if ($file instanceof UploadedFile) { $this->upload($attachment, $file, $options); } elseif ($options['download_url'] && $attachment->isExternal()) { $this->downloadURL($attachment, $options); @@ -210,7 +192,7 @@ class AttachmentSubmitHandler //this is only possible if the attachment is new (not yet persisted to DB) if ($options['become_preview_if_empty'] && null === $attachment->getID() && $attachment->isPicture()) { $element = $attachment->getElement(); - if ($element instanceof AttachmentContainingDBElement && null === $element->getMasterPictureAttachment()) { + if ($element instanceof AttachmentContainingDBElement && !$element->getMasterPictureAttachment() instanceof Attachment) { $element->setMasterPictureAttachment($attachment); } } @@ -220,8 +202,6 @@ class AttachmentSubmitHandler /** * Rename attachments with an unsafe extension (meaning files which would be run by a to a safe one). - * @param Attachment $attachment - * @return Attachment */ protected function renameBlacklistedExtensions(Attachment $attachment): Attachment { @@ -232,7 +212,7 @@ class AttachmentSubmitHandler //Determine the old filepath $old_path = $this->pathResolver->placeholderToRealPath($attachment->getPath()); - if (empty($old_path) || !file_exists($old_path)) { + if ($old_path === null || $old_path === '' || !file_exists($old_path)) { return $attachment; } $filename = basename($old_path); @@ -240,7 +220,7 @@ class AttachmentSubmitHandler //Check if the extension is blacklisted and replace the file extension with txt if needed - if(in_array($ext, self::BLACKLISTED_EXTENSIONS)) { + if(in_array($ext, self::BLACKLISTED_EXTENSIONS, true)) { $new_path = $this->generateAttachmentPath($attachment, $attachment->isSecure()) .DIRECTORY_SEPARATOR.$this->generateAttachmentFilename($attachment, 'txt'); @@ -377,7 +357,7 @@ class AttachmentSubmitHandler //Check if we have an extension given $pathinfo = pathinfo($filename); - if (!empty($pathinfo['extension'])) { + if ($pathinfo['extension'] !== '') { $new_ext = $pathinfo['extension']; } else { //Otherwise we have to guess the extension for the new file, based on its content $new_ext = $this->mimeTypes->getExtensions($this->mimeTypes->guessMimeType($tmp_path))[0] ?? 'tmp'; @@ -391,7 +371,7 @@ class AttachmentSubmitHandler $new_path = $this->pathResolver->realPathToPlaceholder($new_path); //Save the path to the attachment $attachment->setPath($new_path); - } catch (TransportExceptionInterface $transportExceptionInterface) { + } catch (TransportExceptionInterface) { throw new AttachmentDownloadException('Transport error!'); } @@ -428,8 +408,6 @@ class AttachmentSubmitHandler /** * Parses the given file size string and returns the size in bytes. * Taken from https://github.com/symfony/symfony/blob/6.2/src/Symfony/Component/Validator/Constraints/File.php - * @param string $maxSize - * @return int */ private function parseFileSizeString(string $maxSize): int { diff --git a/src/Services/Attachments/AttachmentURLGenerator.php b/src/Services/Attachments/AttachmentURLGenerator.php index 742cce81..afbfade3 100644 --- a/src/Services/Attachments/AttachmentURLGenerator.php +++ b/src/Services/Attachments/AttachmentURLGenerator.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Services\Attachments; +use Imagine\Exception\RuntimeException; use App\Entity\Attachments\Attachment; use InvalidArgumentException; use Liip\ImagineBundle\Imagine\Cache\CacheManager; @@ -30,28 +31,17 @@ use function strlen; use Symfony\Component\Asset\Packages; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +/** + * @see \App\Tests\Services\Attachments\AttachmentURLGeneratorTest + */ class AttachmentURLGenerator { - protected Packages $assets; protected string $public_path; - protected AttachmentPathResolver $pathResolver; - protected UrlGeneratorInterface $urlGenerator; - protected AttachmentManager $attachmentHelper; - protected CacheManager $thumbnailManager; - protected LoggerInterface $logger; - - public function __construct(Packages $assets, AttachmentPathResolver $pathResolver, - UrlGeneratorInterface $urlGenerator, AttachmentManager $attachmentHelper, - CacheManager $thumbnailManager, LoggerInterface $logger) + public function __construct(protected Packages $assets, protected AttachmentPathResolver $pathResolver, + protected UrlGeneratorInterface $urlGenerator, protected AttachmentManager $attachmentHelper, + protected CacheManager $thumbnailManager, protected LoggerInterface $logger) { - $this->assets = $assets; - $this->pathResolver = $pathResolver; - $this->urlGenerator = $urlGenerator; - $this->attachmentHelper = $attachmentHelper; - $this->thumbnailManager = $thumbnailManager; - $this->logger = $logger; - //Determine a normalized path to the public folder (assets are relative to this folder) $this->public_path = $this->pathResolver->parameterToAbsolutePath('public'); } @@ -78,7 +68,7 @@ class AttachmentURLGenerator } //Our absolute path must begin with public path, or we can not use it for asset pathes. - if (0 !== strpos($absolute_path, $public_path)) { + if (!str_starts_with($absolute_path, $public_path)) { return null; } @@ -129,7 +119,7 @@ class AttachmentURLGenerator throw new InvalidArgumentException('Thumbnail creation only works for picture attachments!'); } - if ($attachment->isExternal() && !empty($attachment->getURL())) { + if ($attachment->isExternal() && ($attachment->getURL() !== null && $attachment->getURL() !== '')) { return $attachment->getURL(); } @@ -154,7 +144,7 @@ class AttachmentURLGenerator $tmp = $this->thumbnailManager->getBrowserPath($asset_path, $filter_name, [], null, UrlGeneratorInterface::NETWORK_PATH); //So we remove the schema manually return preg_replace('/^https?:/', '', $tmp); - } catch (\Imagine\Exception\RuntimeException $e) { + } catch (RuntimeException $e) { //If the filter fails, we can not serve the thumbnail and fall back to the original image and log a warning $this->logger->warning('Could not open thumbnail for attachment with ID ' . $attachment->getID() . ': ' . $e->getMessage()); return $this->assets->getUrl($asset_path); diff --git a/src/Services/Attachments/BuiltinAttachmentsFinder.php b/src/Services/Attachments/BuiltinAttachmentsFinder.php index 7c3c8f4b..b009cf60 100644 --- a/src/Services/Attachments/BuiltinAttachmentsFinder.php +++ b/src/Services/Attachments/BuiltinAttachmentsFinder.php @@ -30,16 +30,12 @@ use Symfony\Contracts\Cache\CacheInterface; /** * This service is used to find builtin attachment ressources. + * @see \App\Tests\Services\Attachments\BuiltinAttachmentsFinderTest */ class BuiltinAttachmentsFinder { - protected AttachmentPathResolver $pathResolver; - protected CacheInterface $cache; - - public function __construct(CacheInterface $cache, AttachmentPathResolver $pathResolver) + public function __construct(protected CacheInterface $cache, protected AttachmentPathResolver $pathResolver) { - $this->pathResolver = $pathResolver; - $this->cache = $cache; } /** @@ -49,7 +45,6 @@ class BuiltinAttachmentsFinder * '%FOOTPRINTS%/path/to/folder/file1.png', * '%FOOTPRINTS%/path/to/folder/file2.png', * ] - * @return array */ public function getListOfFootprintsGroupedByFolder(): array { @@ -63,7 +58,7 @@ class BuiltinAttachmentsFinder foreach($finder as $file) { $folder = $file->getRelativePath(); //Normalize path (replace \ with /) - $folder = str_replace('\\', '/', $folder); + $folder = str_replace('\\', '/', (string) $folder); if(!isset($output[$folder])) { $output[$folder] = []; @@ -109,7 +104,7 @@ class BuiltinAttachmentsFinder return $results; }); - } catch (InvalidArgumentException $invalidArgumentException) { + } catch (InvalidArgumentException) { return []; } } @@ -125,7 +120,7 @@ class BuiltinAttachmentsFinder */ public function find(string $keyword, array $options = [], ?array $base_list = []): array { - if (empty($base_list)) { + if ($base_list === null || $base_list === []) { $base_list = $this->getListOfRessources(); } diff --git a/src/Services/Attachments/FileTypeFilterTools.php b/src/Services/Attachments/FileTypeFilterTools.php index 6fc0a162..3380adb7 100644 --- a/src/Services/Attachments/FileTypeFilterTools.php +++ b/src/Services/Attachments/FileTypeFilterTools.php @@ -32,6 +32,7 @@ use Symfony\Contracts\Cache\ItemInterface; * A service that helps to work with filetype filters (based on the format accept uses). * See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#Unique_file_type_specifiers for * more details. + * @see \App\Tests\Services\Attachments\FileTypeFilterToolsTest */ class FileTypeFilterTools { @@ -43,13 +44,8 @@ class FileTypeFilterTools protected const AUDIO_EXTS = ['mp3', 'flac', 'ogg', 'oga', 'wav', 'm4a', 'opus']; protected const ALLOWED_MIME_PLACEHOLDERS = ['image/*', 'audio/*', 'video/*']; - protected MimeTypesInterface $mimeTypes; - protected CacheInterface $cache; - - public function __construct(MimeTypesInterface $mimeTypes, CacheInterface $cache) + public function __construct(protected MimeTypesInterface $mimeTypes, protected CacheInterface $cache) { - $this->mimeTypes = $mimeTypes; - $this->cache = $cache; } /** @@ -73,7 +69,7 @@ class FileTypeFilterTools $element = trim($element); if (!preg_match('#^\.\w+$#', $element) // .ext is allowed && !preg_match('#^[-\w.]+/[-\w.]+#', $element) //Explicit MIME type is allowed - && !in_array($element, static::ALLOWED_MIME_PLACEHOLDERS, false)) { //image/* is allowed + && !in_array($element, static::ALLOWED_MIME_PLACEHOLDERS, true)) { //image/* is allowed return false; } } @@ -108,7 +104,7 @@ class FileTypeFilterTools } //Convert *.jpg to .jpg - if (0 === strpos($element, '*.')) { + if (str_starts_with($element, '*.')) { $element = str_replace('*.', '.', $element); } @@ -119,7 +115,7 @@ class FileTypeFilterTools $element = 'video/*'; } elseif ('audio' === $element || 'audio/' === $element) { $element = 'audio/*'; - } elseif (!preg_match('#^[-\w.]+/[-\w.*]+#', $element) && 0 !== strpos($element, '.')) { + } elseif (!preg_match('#^[-\w.]+/[-\w.*]+#', $element) && !str_starts_with($element, '.')) { //Convert jpg to .jpg $element = '.'.$element; } @@ -147,7 +143,7 @@ class FileTypeFilterTools foreach ($elements as $element) { $element = trim($element); - if (0 === strpos($element, '.')) { + if (str_starts_with($element, '.')) { //We found an explicit specified file extension -> add it to list $extensions[] = substr($element, 1); } elseif ('image/*' === $element) { @@ -177,6 +173,6 @@ class FileTypeFilterTools { $extension = strtolower($extension); - return empty($filter) || in_array($extension, $this->resolveFileExtensions($filter), false); + return $filter === '' || in_array($extension, $this->resolveFileExtensions($filter), true); } } diff --git a/src/Services/Attachments/PartPreviewGenerator.php b/src/Services/Attachments/PartPreviewGenerator.php index 39d1c65c..a9273586 100644 --- a/src/Services/Attachments/PartPreviewGenerator.php +++ b/src/Services/Attachments/PartPreviewGenerator.php @@ -22,16 +22,19 @@ declare(strict_types=1); namespace App\Services\Attachments; +use App\Entity\Parts\Footprint; +use App\Entity\ProjectSystem\Project; +use App\Entity\Parts\Category; +use App\Entity\Parts\Storelocation; +use App\Entity\Parts\MeasurementUnit; +use App\Entity\Parts\Manufacturer; use App\Entity\Attachments\Attachment; use App\Entity\Parts\Part; class PartPreviewGenerator { - protected AttachmentManager $attachmentHelper; - - public function __construct(AttachmentManager $attachmentHelper) + public function __construct(protected AttachmentManager $attachmentHelper) { - $this->attachmentHelper = $attachmentHelper; } /** @@ -55,21 +58,21 @@ class PartPreviewGenerator $list[] = $attachment; } - if (null !== $part->getFootprint()) { + if ($part->getFootprint() instanceof Footprint) { $attachment = $part->getFootprint()->getMasterPictureAttachment(); if ($this->isAttachmentValidPicture($attachment)) { $list[] = $attachment; } } - if (null !== $part->getBuiltProject()) { + if ($part->getBuiltProject() instanceof Project) { $attachment = $part->getBuiltProject()->getMasterPictureAttachment(); if ($this->isAttachmentValidPicture($attachment)) { $list[] = $attachment; } } - if (null !== $part->getCategory()) { + if ($part->getCategory() instanceof Category) { $attachment = $part->getCategory()->getMasterPictureAttachment(); if ($this->isAttachmentValidPicture($attachment)) { $list[] = $attachment; @@ -77,7 +80,7 @@ class PartPreviewGenerator } foreach ($part->getPartLots() as $lot) { - if (null !== $lot->getStorageLocation()) { + if ($lot->getStorageLocation() instanceof Storelocation) { $attachment = $lot->getStorageLocation()->getMasterPictureAttachment(); if ($this->isAttachmentValidPicture($attachment)) { $list[] = $attachment; @@ -85,14 +88,14 @@ class PartPreviewGenerator } } - if (null !== $part->getPartUnit()) { + if ($part->getPartUnit() instanceof MeasurementUnit) { $attachment = $part->getPartUnit()->getMasterPictureAttachment(); if ($this->isAttachmentValidPicture($attachment)) { $list[] = $attachment; } } - if (null !== $part->getManufacturer()) { + if ($part->getManufacturer() instanceof Manufacturer) { $attachment = $part->getManufacturer()->getMasterPictureAttachment(); if ($this->isAttachmentValidPicture($attachment)) { $list[] = $attachment; @@ -117,7 +120,7 @@ class PartPreviewGenerator } //Otherwise check if the part has a footprint with a valid master attachment - if (null !== $part->getFootprint()) { + if ($part->getFootprint() instanceof Footprint) { $attachment = $part->getFootprint()->getMasterPictureAttachment(); if ($this->isAttachmentValidPicture($attachment)) { return $attachment; @@ -125,7 +128,7 @@ class PartPreviewGenerator } //With lowest priority use the master attachment of the project this part represents (when existing) - if (null !== $part->getBuiltProject()) { + if ($part->getBuiltProject() instanceof Project) { $attachment = $part->getBuiltProject()->getMasterPictureAttachment(); if ($this->isAttachmentValidPicture($attachment)) { return $attachment; @@ -145,7 +148,7 @@ class PartPreviewGenerator */ protected function isAttachmentValidPicture(?Attachment $attachment): bool { - return null !== $attachment + return $attachment instanceof Attachment && $attachment->isPicture() && $this->attachmentHelper->isFileExisting($attachment); } diff --git a/src/Services/CustomEnvVarProcessor.php b/src/Services/CustomEnvVarProcessor.php index 8969b765..f269cc7d 100644 --- a/src/Services/CustomEnvVarProcessor.php +++ b/src/Services/CustomEnvVarProcessor.php @@ -35,7 +35,7 @@ final class CustomEnvVarProcessor implements EnvVarProcessorInterface $env = $getEnv($name); return !empty($env) && 'null://null' !== $env; - } catch (EnvNotFoundException $envNotFoundException) { + } catch (EnvNotFoundException) { return false; } } diff --git a/src/Services/ElementTypeNameGenerator.php b/src/Services/ElementTypeNameGenerator.php index cbe8344f..f32677eb 100644 --- a/src/Services/ElementTypeNameGenerator.php +++ b/src/Services/ElementTypeNameGenerator.php @@ -22,9 +22,11 @@ declare(strict_types=1); namespace App\Services; +use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractDBElement; +use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Contracts\NamedElementInterface; use App\Entity\ProjectSystem\Project; use App\Entity\LabelSystem\LabelProfile; @@ -48,17 +50,15 @@ use Doctrine\ORM\Mapping\Entity; use function get_class; use Symfony\Contracts\Translation\TranslatorInterface; +/** + * @see \App\Tests\Services\ElementTypeNameGeneratorTest + */ class ElementTypeNameGenerator { - protected TranslatorInterface $translator; - private EntityURLGenerator $entityURLGenerator; protected array $mapping; - public function __construct(TranslatorInterface $translator, EntityURLGenerator $entityURLGenerator) + public function __construct(protected TranslatorInterface $translator, private readonly EntityURLGenerator $entityURLGenerator) { - $this->translator = $translator; - $this->entityURLGenerator = $entityURLGenerator; - //Child classes has to become before parent classes $this->mapping = [ Attachment::class => $this->translator->trans('attachment.label'), @@ -95,9 +95,9 @@ class ElementTypeNameGenerator * * @throws EntityNotSupportedException when the passed entity is not supported */ - public function getLocalizedTypeLabel($entity): string + public function getLocalizedTypeLabel(object|string $entity): string { - $class = is_string($entity) ? $entity : get_class($entity); + $class = is_string($entity) ? $entity : $entity::class; //Check if we have a direct array entry for our entity class, then we can use it if (isset($this->mapping[$class])) { @@ -105,14 +105,14 @@ class ElementTypeNameGenerator } //Otherwise iterate over array and check for inheritance (needed when the proxy element from doctrine are passed) - foreach ($this->mapping as $class => $translation) { - if (is_a($entity, $class, true)) { + foreach ($this->mapping as $class_to_check => $translation) { + if (is_a($entity, $class_to_check, true)) { return $translation; } } //When nothing was found throw an exception - throw new EntityNotSupportedException(sprintf('No localized label for the element with type %s was found!', is_object($entity) ? get_class($entity) : (string) $entity)); + throw new EntityNotSupportedException(sprintf('No localized label for the element with type %s was found!', is_object($entity) ? $entity::class : (string) $entity)); } /** @@ -131,7 +131,7 @@ class ElementTypeNameGenerator { $type = $this->getLocalizedTypeLabel($entity); if ($use_html) { - return ''.$type.': '.htmlspecialchars($entity->getName()); + return ''.$type.': '.htmlspecialchars((string) $entity->getName()); } return $type.': '.$entity->getName(); @@ -143,19 +143,18 @@ class ElementTypeNameGenerator * "Type: ID" (on elements without a name). If possible the value is given as a link to the element. * @param AbstractDBElement $entity The entity for which the label should be generated * @param bool $include_associated If set to true, the associated entity (like the part belonging to a part lot) is included in the label to give further information - * @return string */ public function formatLabelHTMLForEntity(AbstractDBElement $entity, bool $include_associated = false): string { //The element is existing - if ($entity instanceof NamedElementInterface && !empty($entity->getName())) { + if ($entity instanceof NamedElementInterface && $entity->getName() !== '') { try { $tmp = sprintf( '%s', $this->entityURLGenerator->infoURL($entity), $this->getTypeNameCombination($entity, true) ); - } catch (EntityNotSupportedException $exception) { + } catch (EntityNotSupportedException) { $tmp = $this->getTypeNameCombination($entity, true); } } else { //Target does not have a name @@ -168,28 +167,28 @@ class ElementTypeNameGenerator //Add a hint to the associated element if possible if ($include_associated) { - if ($entity instanceof Attachment && null !== $entity->getElement()) { + if ($entity instanceof Attachment && $entity->getElement() instanceof AttachmentContainingDBElement) { $on = $entity->getElement(); - } elseif ($entity instanceof AbstractParameter && null !== $entity->getElement()) { + } elseif ($entity instanceof AbstractParameter && $entity->getElement() instanceof AbstractDBElement) { $on = $entity->getElement(); - } elseif ($entity instanceof PartLot && null !== $entity->getPart()) { + } elseif ($entity instanceof PartLot && $entity->getPart() instanceof Part) { $on = $entity->getPart(); - } elseif ($entity instanceof Orderdetail && null !== $entity->getPart()) { + } elseif ($entity instanceof Orderdetail && $entity->getPart() instanceof Part) { $on = $entity->getPart(); - } elseif ($entity instanceof Pricedetail && null !== $entity->getOrderdetail() && null !== $entity->getOrderdetail()->getPart()) { + } elseif ($entity instanceof Pricedetail && $entity->getOrderdetail() instanceof Orderdetail && $entity->getOrderdetail()->getPart() instanceof Part) { $on = $entity->getOrderdetail()->getPart(); - } elseif ($entity instanceof ProjectBOMEntry && null !== $entity->getProject()) { + } elseif ($entity instanceof ProjectBOMEntry && $entity->getProject() instanceof Project) { $on = $entity->getProject(); } - if (isset($on) && is_object($on)) { + if (isset($on) && $on instanceof NamedElementInterface) { try { $tmp .= sprintf( ' (%s)', $this->entityURLGenerator->infoURL($on), $this->getTypeNameCombination($on, true) ); - } catch (EntityNotSupportedException $exception) { + } catch (EntityNotSupportedException) { } } } @@ -200,9 +199,6 @@ class ElementTypeNameGenerator /** * Create a HTML formatted label for a deleted element of which we only know the class and the ID. * Please note that it is not checked if the element really not exists anymore, so you have to do this yourself. - * @param string $class - * @param int $id - * @return string */ public function formatElementDeletedHTML(string $class, int $id): string { diff --git a/src/Services/EntityURLGenerator.php b/src/Services/EntityURLGenerator.php index 5f45e58c..30f56e5d 100644 --- a/src/Services/EntityURLGenerator.php +++ b/src/Services/EntityURLGenerator.php @@ -57,13 +57,8 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; */ class EntityURLGenerator { - protected UrlGeneratorInterface $urlGenerator; - protected AttachmentURLGenerator $attachmentURLGenerator; - - public function __construct(UrlGeneratorInterface $urlGenerator, AttachmentURLGenerator $attachmentURLGenerator) + public function __construct(protected UrlGeneratorInterface $urlGenerator, protected AttachmentURLGenerator $attachmentURLGenerator) { - $this->urlGenerator = $urlGenerator; - $this->attachmentURLGenerator = $attachmentURLGenerator; } /** @@ -79,29 +74,19 @@ class EntityURLGenerator * @throws EntityNotSupportedException thrown if the entity is not supported for the given type * @throws InvalidArgumentException thrown if the givent type is not existing */ - public function getURL($entity, string $type): string + public function getURL(mixed $entity, string $type): string { - switch ($type) { - case 'info': - return $this->infoURL($entity); - case 'edit': - return $this->editURL($entity); - case 'create': - return $this->createURL($entity); - case 'clone': - return $this->cloneURL($entity); - case 'list': - case 'list_parts': - return $this->listPartsURL($entity); - case 'delete': - return $this->deleteURL($entity); - case 'file_download': - return $this->downloadURL($entity); - case 'file_view': - return $this->viewURL($entity); - } - - throw new InvalidArgumentException('Method is not supported!'); + return match ($type) { + 'info' => $this->infoURL($entity), + 'edit' => $this->editURL($entity), + 'create' => $this->createURL($entity), + 'clone' => $this->cloneURL($entity), + 'list', 'list_parts' => $this->listPartsURL($entity), + 'delete' => $this->deleteURL($entity), + 'file_download' => $this->downloadURL($entity), + 'file_view' => $this->viewURL($entity), + default => throw new InvalidArgumentException('Method is not supported!'), + }; } /** @@ -134,7 +119,7 @@ class EntityURLGenerator 'timestamp' => $dateTime->getTimestamp(), ] ); - } catch (EntityNotSupportedException $exception) { + } catch (EntityNotSupportedException) { if ($entity instanceof PartLot) { return $this->urlGenerator->generate('part_info', [ 'id' => $entity->getPart()->getID(), @@ -168,13 +153,13 @@ class EntityURLGenerator } //Otherwise throw an error - throw new EntityNotSupportedException('The given entity is not supported yet! Passed class type: '.get_class($entity)); + throw new EntityNotSupportedException('The given entity is not supported yet! Passed class type: '.$entity::class); } public function viewURL(Attachment $entity): string { if ($entity->isExternal()) { //For external attachments, return the link to external path - return $entity->getURL(); + return $entity->getURL() ?? throw new \RuntimeException('External attachment has no URL!'); } //return $this->urlGenerator->generate('attachment_view', ['id' => $entity->getID()]); return $this->attachmentURLGenerator->getViewURL($entity) ?? ''; @@ -184,14 +169,14 @@ class EntityURLGenerator { if ($entity instanceof Attachment) { if ($entity->isExternal()) { //For external attachments, return the link to external path - return $entity->getURL(); + return $entity->getURL() ?? throw new \RuntimeException('External attachment has no URL!'); } - return $this->attachmentURLGenerator->getDownloadURL($entity) ?? ''; + return $this->attachmentURLGenerator->getDownloadURL($entity); } //Otherwise throw an error - throw new EntityNotSupportedException(sprintf('The given entity is not supported yet! Passed class type: %s', get_class($entity))); + throw new EntityNotSupportedException(sprintf('The given entity is not supported yet! Passed class type: %s', $entity::class)); } /** @@ -235,7 +220,7 @@ class EntityURLGenerator * * @throws EntityNotSupportedException If the method is not supported for the given Entity */ - public function editURL($entity): string + public function editURL(mixed $entity): string { $map = [ Part::class => 'part_edit', @@ -265,7 +250,7 @@ class EntityURLGenerator * * @throws EntityNotSupportedException If the method is not supported for the given Entity */ - public function createURL($entity): string + public function createURL(mixed $entity): string { $map = [ Part::class => 'part_new', @@ -373,9 +358,9 @@ class EntityURLGenerator * * @throws EntityNotSupportedException */ - protected function mapToController(array $map, $entity): string + protected function mapToController(array $map, mixed $entity): string { - $class = get_class($entity); + $class = $entity::class; //Check if we have an direct mapping for the given class if (!array_key_exists($class, $map)) { @@ -386,7 +371,7 @@ class EntityURLGenerator } } - throw new EntityNotSupportedException(sprintf('The given entity is not supported yet! Passed class type: %s', get_class($entity))); + throw new EntityNotSupportedException(sprintf('The given entity is not supported yet! Passed class type: %s', $entity::class)); } return $map[$class]; diff --git a/src/Services/Formatters/AmountFormatter.php b/src/Services/Formatters/AmountFormatter.php index ccc4c4b3..73d59113 100644 --- a/src/Services/Formatters/AmountFormatter.php +++ b/src/Services/Formatters/AmountFormatter.php @@ -29,28 +29,24 @@ use Symfony\Component\OptionsResolver\OptionsResolver; /** * This service formats a part amount using a Measurement Unit. + * @see \App\Tests\Services\Formatters\AmountFormatterTest */ class AmountFormatter { - protected SIFormatter $siFormatter; - - public function __construct(SIFormatter $siFormatter) + public function __construct(protected SIFormatter $siFormatter) { - $this->siFormatter = $siFormatter; } /** * Formats the given value using the measurement unit and options. * - * @param float|string|int $value * @param MeasurementUnit|null $unit The measurement unit, whose unit symbol should be used for formatting. * If set to null, it is assumed that the part amount is measured in pieces. * * @return string The formatted string - * * @throws InvalidArgumentException thrown if $value is not numeric */ - public function format($value, ?MeasurementUnit $unit = null, array $options = []): string + public function format(float|string|int $value, ?MeasurementUnit $unit = null, array $options = []): string { if (!is_numeric($value)) { throw new InvalidArgumentException('$value must be an numeric value!'); diff --git a/src/Services/Formatters/MarkdownParser.php b/src/Services/Formatters/MarkdownParser.php index 1c1d8ff8..f3ef07df 100644 --- a/src/Services/Formatters/MarkdownParser.php +++ b/src/Services/Formatters/MarkdownParser.php @@ -29,11 +29,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class MarkdownParser { - protected TranslatorInterface $translator; - - public function __construct(TranslatorInterface $translator) + public function __construct(protected TranslatorInterface $translator) { - $this->translator = $translator; } /** diff --git a/src/Services/Formatters/MoneyFormatter.php b/src/Services/Formatters/MoneyFormatter.php index 1907fe90..d49b77cf 100644 --- a/src/Services/Formatters/MoneyFormatter.php +++ b/src/Services/Formatters/MoneyFormatter.php @@ -28,12 +28,10 @@ use NumberFormatter; class MoneyFormatter { - protected string $base_currency; protected string $locale; - public function __construct(string $base_currency) + public function __construct(protected string $base_currency) { - $this->base_currency = $base_currency; $this->locale = Locale::getDefault(); } @@ -45,10 +43,10 @@ class MoneyFormatter * @param int $decimals the number of decimals that should be shown * @param bool $show_all_digits if set to true, all digits are shown, even if they are null */ - public function format($value, ?Currency $currency = null, int $decimals = 5, bool $show_all_digits = false): string + public function format(string|float $value, ?Currency $currency = null, int $decimals = 5, bool $show_all_digits = false): string { $iso_code = $this->base_currency; - if (null !== $currency && !empty($currency->getIsoCode())) { + if ($currency instanceof Currency && ($currency->getIsoCode() !== null && $currency->getIsoCode() !== '')) { $iso_code = $currency->getIsoCode(); } diff --git a/src/Services/Formatters/SIFormatter.php b/src/Services/Formatters/SIFormatter.php index 619c2de3..a6325987 100644 --- a/src/Services/Formatters/SIFormatter.php +++ b/src/Services/Formatters/SIFormatter.php @@ -24,6 +24,7 @@ namespace App\Services\Formatters; /** * A service that helps you to format values using the SI prefixes. + * @see \App\Tests\Services\Formatters\SIFormatterTest */ class SIFormatter { @@ -93,11 +94,7 @@ class SIFormatter [$divisor, $symbol] = $this->getPrefixByMagnitude($this->getMagnitude($value)); $value /= $divisor; //Build the format string, e.g.: %.2d km - if ('' !== $unit || '' !== $symbol) { - $format_string = '%.'.$decimals.'f '.$symbol.$unit; - } else { - $format_string = '%.'.$decimals.'f'; - } + $format_string = '' !== $unit || '' !== $symbol ? '%.'.$decimals.'f '.$symbol.$unit : '%.'.$decimals.'f'; return sprintf($format_string, $value); } diff --git a/src/Services/ImportExportSystem/BOMImporter.php b/src/Services/ImportExportSystem/BOMImporter.php index 72fa84d5..89b62660 100644 --- a/src/Services/ImportExportSystem/BOMImporter.php +++ b/src/Services/ImportExportSystem/BOMImporter.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\ImportExportSystem; use App\Entity\ProjectSystem\Project; @@ -27,6 +29,9 @@ use League\Csv\Reader; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\OptionsResolver\OptionsResolver; +/** + * @see \App\Tests\Services\ImportExportSystem\BOMImporterTest + */ class BOMImporter { @@ -54,9 +59,6 @@ class BOMImporter /** * Converts the given file into an array of BOM entries using the given options and save them into the given project. * The changes are not saved into the database yet. - * @param File $file - * @param array $options - * @param Project $project * @return ProjectBOMEntry[] */ public function importFileIntoProject(File $file, Project $project, array $options): array @@ -73,8 +75,6 @@ class BOMImporter /** * Converts the given file into an array of BOM entries using the given options. - * @param File $file - * @param array $options * @return ProjectBOMEntry[] */ public function fileToBOMEntries(File $file, array $options): array @@ -94,12 +94,10 @@ class BOMImporter $resolver = $this->configureOptions($resolver); $options = $resolver->resolve($options); - switch ($options['type']) { - case 'kicad_pcbnew': - return $this->parseKiCADPCB($data, $options); - default: - throw new InvalidArgumentException('Invalid import type!'); - } + return match ($options['type']) { + 'kicad_pcbnew' => $this->parseKiCADPCB($data, $options), + default => throw new InvalidArgumentException('Invalid import type!'), + }; } private function parseKiCADPCB(string $data, array $options = []): array @@ -112,9 +110,7 @@ class BOMImporter foreach ($csv->getRecords() as $offset => $entry) { //Translate the german field names to english - $entry = array_combine(array_map(static function ($key) { - return self::MAP_KICAD_PCB_FIELDS[$key] ?? $key; - }, array_keys($entry)), $entry); + $entry = array_combine(array_map(static fn($key) => self::MAP_KICAD_PCB_FIELDS[$key] ?? $key, array_keys($entry)), $entry); //Ensure that the entry has all required fields if (!isset ($entry['Designator'])) { @@ -141,4 +137,4 @@ class BOMImporter return $bom_entries; } -} \ No newline at end of file +} diff --git a/src/Services/ImportExportSystem/EntityExporter.php b/src/Services/ImportExportSystem/EntityExporter.php index a4825869..50b6b7cc 100644 --- a/src/Services/ImportExportSystem/EntityExporter.php +++ b/src/Services/ImportExportSystem/EntityExporter.php @@ -36,14 +36,12 @@ use function Symfony\Component\String\u; /** * Use this class to export an entity to multiple file formats. + * @see \App\Tests\Services\ImportExportSystem\EntityExporterTest */ class EntityExporter { - protected SerializerInterface $serializer; - - public function __construct(SerializerInterface $serializer) + public function __construct(protected SerializerInterface $serializer) { - $this->serializer = $serializer; } protected function configureOptions(OptionsResolver $resolver): void @@ -67,7 +65,7 @@ class EntityExporter * @param array $options The options to use for exporting * @return string The serialized data */ - public function exportEntities($entities, array $options): string + public function exportEntities(AbstractNamedDBElement|array $entities, array $options): string { if (!is_array($entities)) { $entities = [$entities]; @@ -111,7 +109,7 @@ class EntityExporter * * @throws ReflectionException */ - public function exportEntityFromRequest($entities, Request $request): Response + public function exportEntityFromRequest(AbstractNamedDBElement|array $entities, Request $request): Response { $options = [ 'format' => $request->get('format') ?? 'json', diff --git a/src/Services/ImportExportSystem/EntityImporter.php b/src/Services/ImportExportSystem/EntityImporter.php index c798f9f5..0b4e0f17 100644 --- a/src/Services/ImportExportSystem/EntityImporter.php +++ b/src/Services/ImportExportSystem/EntityImporter.php @@ -26,6 +26,8 @@ use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Parts\Category; use App\Entity\Parts\Part; +use Composer\Semver\Constraint\Constraint; +use Symfony\Component\Validator\ConstraintViolationList; use Symplify\EasyCodingStandard\ValueObject\Option; use function count; use Doctrine\ORM\EntityManagerInterface; @@ -36,17 +38,13 @@ use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; +/** + * @see \App\Tests\Services\ImportExportSystem\EntityImporterTest + */ class EntityImporter { - protected SerializerInterface $serializer; - protected EntityManagerInterface $em; - protected ValidatorInterface $validator; - - public function __construct(SerializerInterface $serializer, EntityManagerInterface $em, ValidatorInterface $validator) + public function __construct(protected SerializerInterface $serializer, protected EntityManagerInterface $em, protected ValidatorInterface $validator) { - $this->serializer = $serializer; - $this->em = $em; - $this->validator = $validator; } /** @@ -68,7 +66,7 @@ class EntityImporter if (!is_a($class_name, AbstractNamedDBElement::class, true)) { throw new InvalidArgumentException('$class_name must be a StructuralDBElement type!'); } - if (null !== $parent && !is_a($parent, $class_name)) { + if ($parent instanceof AbstractStructuralDBElement && !$parent instanceof $class_name) { throw new InvalidArgumentException('$parent must have the same type as specified in $class_name!'); } @@ -92,11 +90,7 @@ class EntityImporter } while ($identSize < end($indentations)) { //If the line is intendet less than the last line, we have to go up in the tree - if ($current_parent instanceof AbstractStructuralDBElement) { - $current_parent = $current_parent->getParent(); - } else { - $current_parent = null; - } + $current_parent = $current_parent instanceof AbstractStructuralDBElement ? $current_parent->getParent() : null; array_pop($indentations); } @@ -246,7 +240,7 @@ class EntityImporter * @param array $options options for the import process * @param AbstractNamedDBElement[] $entities The imported entities are returned in this array * - * @return array An associative array containing an ConstraintViolationList and the entity name as key are returned, + * @return array An associative array containing an ConstraintViolationList and the entity name as key are returned, * if an error happened during validation. When everything was successfully, the array should be empty. */ public function importFileAndPersistToDB(File $file, array $options = [], array &$entities = []): array @@ -297,20 +291,13 @@ class EntityImporter //Convert the extension to lower case $extension = strtolower($extension); - switch ($extension) { - case 'json': - return 'json'; - case 'xml': - return 'xml'; - case 'csv': - case 'tsv': - return 'csv'; - case 'yaml': - case 'yml': - return 'yaml'; - default: - return null; - } + return match ($extension) { + 'json' => 'json', + 'xml' => 'xml', + 'csv', 'tsv' => 'csv', + 'yaml', 'yml' => 'yaml', + default => null, + }; } /** diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/MySQLDumpXMLConverter.php b/src/Services/ImportExportSystem/PartKeeprImporter/MySQLDumpXMLConverter.php index 4ff9c189..f221ee89 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/MySQLDumpXMLConverter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/MySQLDumpXMLConverter.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\ImportExportSystem\PartKeeprImporter; class MySQLDumpXMLConverter @@ -107,4 +109,4 @@ class MySQLDumpXMLConverter return $row_data; } -} \ No newline at end of file +} diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php index 52732a44..d53566c0 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKDatastructureImporter.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\ImportExportSystem\PartKeeprImporter; use App\Doctrine\Purger\ResetAutoIncrementORMPurger; @@ -87,7 +89,7 @@ class PKDatastructureImporter $this->em->flush(); - return count($distributor_data); + return is_countable($distributor_data) ? count($distributor_data) : 0; } public function importManufacturers(array $data): int @@ -130,7 +132,7 @@ class PKDatastructureImporter $this->importAttachments($data, 'manufacturericlogo', Manufacturer::class, 'manufacturer_id', ManufacturerAttachment::class); - return count($manufacturer_data); + return is_countable($manufacturer_data) ? count($manufacturer_data) : 0; } public function importPartUnits(array $data): int @@ -151,7 +153,7 @@ class PKDatastructureImporter $this->em->flush(); - return count($partunit_data); + return is_countable($partunit_data) ? count($partunit_data) : 0; } public function importCategories(array $data): int @@ -180,15 +182,11 @@ class PKDatastructureImporter } $this->em->flush(); - return count($partcategory_data); + return is_countable($partcategory_data) ? count($partcategory_data) : 0; } /** * The common import functions for footprints and storeloactions - * @param array $data - * @param string $target_class - * @param string $data_prefix - * @return int */ private function importElementsWithCategory(array $data, string $target_class, string $data_prefix): int { @@ -249,7 +247,7 @@ class PKDatastructureImporter $this->em->flush(); - return count($footprint_data) + count($footprintcategory_data); + return (is_countable($footprint_data) ? count($footprint_data) : 0) + (is_countable($footprintcategory_data) ? count($footprintcategory_data) : 0); } public function importFootprints(array $data): int @@ -272,4 +270,4 @@ class PKDatastructureImporter return $count; } -} \ No newline at end of file +} diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelper.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelper.php index 3709fac2..f36e48ce 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelper.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelper.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\ImportExportSystem\PartKeeprImporter; use App\Doctrine\Purger\ResetAutoIncrementORMPurger; @@ -28,18 +30,14 @@ use Doctrine\ORM\EntityManagerInterface; */ class PKImportHelper { - protected EntityManagerInterface $em; - - public function __construct(EntityManagerInterface $em) + public function __construct(protected EntityManagerInterface $em) { - $this->em = $em; } /** * Purges the database tables for the import, so that all data can be created from scratch. * Existing users and groups are not purged. * This is needed to avoid ID collisions. - * @return void */ public function purgeDatabaseForImport(): void { @@ -50,8 +48,6 @@ class PKImportHelper /** * Extracts the current database schema version from the PartKeepr XML dump. - * @param array $data - * @return string */ public function getDatabaseSchemaVersion(array $data): string { @@ -64,11 +60,10 @@ class PKImportHelper /** * Checks that the database schema of the PartKeepr XML dump is compatible with the importer - * @param array $data * @return bool True if the schema is compatible, false otherwise */ public function checkVersion(array $data): bool { return $this->getDatabaseSchemaVersion($data) === '20170601175559'; } -} \ No newline at end of file +} diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php index a318c281..70237114 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKImportHelperTrait.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\ImportExportSystem\PartKeeprImporter; +use Doctrine\ORM\Id\AssignedGenerator; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\AttachmentType; @@ -45,7 +48,6 @@ trait PKImportHelperTrait * @param array $attachment_row The attachment row from the PartKeepr database * @param string $target_class The target class for the attachment * @param string $type The type of the attachment (attachment or image) - * @return Attachment * @throws \Exception */ protected function convertAttachmentDataToEntity(array $attachment_row, string $target_class, string $type): Attachment @@ -87,10 +89,10 @@ trait PKImportHelperTrait //Use mime type to determine the extension like PartKeepr does in legacy implementation (just use the second part of the mime type) //See UploadedFile.php:291 in PartKeepr (https://github.com/partkeepr/PartKeepr/blob/f6176c3354b24fa39ac8bc4328ee0df91de3d5b6/src/PartKeepr/UploadedFileBundle/Entity/UploadedFile.php#L291) if (!empty ($attachment_row['mimetype'])) { - $attachment_row['extension'] = explode('/', $attachment_row['mimetype'])[1]; + $attachment_row['extension'] = explode('/', (string) $attachment_row['mimetype'])[1]; } else { //If the mime type is empty, we use the original extension - $attachment_row['extension'] = pathinfo($attachment_row['originalname'], PATHINFO_EXTENSION); + $attachment_row['extension'] = pathinfo((string) $attachment_row['originalname'], PATHINFO_EXTENSION); } } @@ -115,7 +117,6 @@ trait PKImportHelperTrait * @param string $target_class The target class (e.g. Part) * @param string $target_id_field The field name where the target ID is stored * @param string $attachment_class The attachment class (e.g. PartAttachment) - * @return void */ protected function importAttachments(array $data, string $table_name, string $target_class, string $target_id_field, string $attachment_class): void { @@ -156,12 +157,9 @@ trait PKImportHelperTrait /** * Assigns the parent to the given entity, using the numerical IDs from the imported data. - * @param string $class - * @param int|string $element_id - * @param int|string $parent_id * @return AbstractStructuralDBElement The structural element that was modified (with $element_id) */ - protected function setParent(string $class, $element_id, $parent_id): AbstractStructuralDBElement + protected function setParent(string $class, int|string $element_id, int|string $parent_id): AbstractStructuralDBElement { $element = $this->em->find($class, (int) $element_id); if (!$element) { @@ -184,7 +182,6 @@ trait PKImportHelperTrait /** * Sets the given field of the given entity to the entity with the given ID. - * @return AbstractDBElement */ protected function setAssociationField(AbstractDBElement $element, string $field, string $other_class, $other_id): AbstractDBElement { @@ -205,11 +202,8 @@ trait PKImportHelperTrait /** * Set the ID of an entity to a specific value. Must be called before persisting the entity, but before flushing. - * @param AbstractDBElement $element - * @param int|string $id - * @return void */ - protected function setIDOfEntity(AbstractDBElement $element, $id): void + protected function setIDOfEntity(AbstractDBElement $element, int|string $id): void { if (!is_int($id) && !is_string($id)) { throw new \InvalidArgumentException('ID must be an integer or string'); @@ -217,9 +211,9 @@ trait PKImportHelperTrait $id = (int) $id; - $metadata = $this->em->getClassMetadata(get_class($element)); + $metadata = $this->em->getClassMetadata($element::class); $metadata->setIdGeneratorType(ClassMetadataInfo::GENERATOR_TYPE_NONE); - $metadata->setIdGenerator(new \Doctrine\ORM\Id\AssignedGenerator()); + $metadata->setIdGenerator(new AssignedGenerator()); $metadata->setIdentifierValues($element, ['id' => $id]); } @@ -241,4 +235,4 @@ trait PKImportHelperTrait $property->setAccessible(true); $property->setValue($entity, $date); } -} \ No newline at end of file +} diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php index 4d3af4f4..b8e8272e 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKOptionalImporter.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\ImportExportSystem\PartKeeprImporter; use App\Entity\Attachments\ProjectAttachment; @@ -45,7 +47,6 @@ class PKOptionalImporter /** * Import the projects from the given data. - * @param array $data * @return int The number of imported projects */ public function importProjects(array $data): int @@ -99,12 +100,11 @@ class PKOptionalImporter $this->importAttachments($data, 'projectattachment', Project::class, 'project_id', ProjectAttachment::class); - return count($projects_data); + return is_countable($projects_data) ? count($projects_data) : 0; } /** * Import the users from the given data. - * @param array $data * @return int The number of imported users */ public function importUsers(array $data): int @@ -144,6 +144,6 @@ class PKOptionalImporter $this->em->flush(); - return count($users_data); + return is_countable($users_data) ? count($users_data) : 0; } -} \ No newline at end of file +} diff --git a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php index 3e00b033..a1b9aeed 100644 --- a/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php +++ b/src/Services/ImportExportSystem/PartKeeprImporter/PKPartImporter.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\ImportExportSystem\PartKeeprImporter; use App\Entity\Attachments\PartAttachment; @@ -45,13 +47,10 @@ class PKPartImporter { use PKImportHelperTrait; - private string $base_currency; - - public function __construct(EntityManagerInterface $em, PropertyAccessorInterface $propertyAccessor, string $default_currency) + public function __construct(EntityManagerInterface $em, PropertyAccessorInterface $propertyAccessor, private readonly string $base_currency) { $this->em = $em; $this->propertyAccessor = $propertyAccessor; - $this->base_currency = $default_currency; } public function importParts(array $data): int @@ -128,7 +127,7 @@ class PKPartImporter //Import attachments $this->importAttachments($data, 'partattachment', Part::class, 'part_id', PartAttachment::class); - return count($part_data); + return is_countable($part_data) ? count($part_data) : 0; } protected function importPartManufacturers(array $data): void @@ -146,7 +145,7 @@ class PKPartImporter throw new \RuntimeException(sprintf('Could not find part with ID %s', $partmanufacturer['part_id'])); } $manufacturer = $this->em->find(Manufacturer::class, (int) $partmanufacturer['manufacturer_id']); - if (!$manufacturer) { + if (!$manufacturer instanceof Manufacturer) { throw new \RuntimeException(sprintf('Could not find manufacturer with ID %s', $partmanufacturer['manufacturer_id'])); } $part->setManufacturer($manufacturer); @@ -190,7 +189,7 @@ class PKPartImporter } $part = $this->em->find(Part::class, (int) $partparameter['part_id']); - if (!$part) { + if (!$part instanceof Part) { throw new \RuntimeException(sprintf('Could not find part with ID %s', $partparameter['part_id'])); } @@ -203,8 +202,6 @@ class PKPartImporter /** * Returns the currency for the given ISO code. If the currency does not exist, it is created. * This function returns null if the ISO code is the base currency. - * @param string $currency_iso_code - * @return Currency|null */ protected function getOrCreateCurrency(string $currency_iso_code): ?Currency { @@ -240,12 +237,12 @@ class PKPartImporter foreach ($data['partdistributor'] as $partdistributor) { //Retrieve the part $part = $this->em->find(Part::class, (int) $partdistributor['part_id']); - if (!$part) { + if (!$part instanceof Part) { throw new \RuntimeException(sprintf('Could not find part with ID %s', $partdistributor['part_id'])); } //Retrieve the distributor $supplier = $this->em->find(Supplier::class, (int) $partdistributor['distributor_id']); - if (!$supplier) { + if (!$supplier instanceof Supplier) { throw new \RuntimeException(sprintf('Could not find supplier with ID %s', $partdistributor['distributor_id'])); } @@ -305,9 +302,6 @@ class PKPartImporter /** * Returns the (parameter) unit symbol for the given ID. - * @param array $data - * @param int $id - * @return string */ protected function getUnitSymbol(array $data, int $id): string { @@ -319,4 +313,4 @@ class PKPartImporter throw new \RuntimeException(sprintf('Could not find unit with ID %s', $id)); } -} \ No newline at end of file +} diff --git a/src/Services/LabelSystem/BarcodeGenerator.php b/src/Services/LabelSystem/BarcodeGenerator.php index d88210ca..f955955a 100644 --- a/src/Services/LabelSystem/BarcodeGenerator.php +++ b/src/Services/LabelSystem/BarcodeGenerator.php @@ -43,19 +43,19 @@ namespace App\Services\LabelSystem; use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\LabelSystem\BarcodeType; use App\Entity\LabelSystem\LabelOptions; use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator; use Com\Tecnick\Barcode\Barcode; use InvalidArgumentException; +/** + * @see \App\Tests\Services\LabelSystem\BarcodeGeneratorTest + */ final class BarcodeGenerator { - private BarcodeContentGenerator $barcodeContentGenerator; - - - public function __construct(BarcodeContentGenerator $barcodeContentGenerator) + public function __construct(private readonly BarcodeContentGenerator $barcodeContentGenerator) { - $this->barcodeContentGenerator = $barcodeContentGenerator; } public function generateHTMLBarcode(LabelOptions $options, object $target): ?string @@ -79,7 +79,7 @@ final class BarcodeGenerator $repr = 'data:'; $repr .= $mime; - if (0 === strpos($mime, 'text/')) { + if (str_starts_with($mime, 'text/')) { $repr .= ','.rawurlencode($data); } else { $repr .= ';base64,'.base64_encode($data); @@ -92,52 +92,31 @@ final class BarcodeGenerator { $barcode = new Barcode(); - switch ($options->getBarcodeType()) { - case 'qr': - $type = 'QRCODE'; + $type = match ($options->getBarcodeType()) { + BarcodeType::NONE => null, + BarcodeType::QR => 'QRCODE', + BarcodeType::DATAMATRIX => 'DATAMATRIX', + BarcodeType::CODE39 => 'C39', + BarcodeType::CODE93 => 'C93', + BarcodeType::CODE128 => 'C128A', + }; - break; - case 'datamatrix': - $type = 'DATAMATRIX'; - - break; - case 'code39': - $type = 'C39'; - - break; - case 'code93': - $type = 'C93'; - - break; - case 'code128': - $type = 'C128A'; - - break; - case 'none': - return null; - default: - throw new InvalidArgumentException('Unknown label type!'); + if ($type === null) { + return null; } - $bobj = $barcode->getBarcodeObj($type, $this->getContent($options, $target)); - return $bobj->getSvgCode(); + return $barcode->getBarcodeObj($type, $this->getContent($options, $target))->getSvgCode(); } public function getContent(LabelOptions $options, AbstractDBElement $target): ?string { - switch ($options->getBarcodeType()) { - case 'qr': - case 'datamatrix': - return $this->barcodeContentGenerator->getURLContent($target); - case 'code39': - case 'code93': - case 'code128': - return $this->barcodeContentGenerator->get1DBarcodeContent($target); - case 'none': - return null; - default: - throw new InvalidArgumentException('Unknown label type!'); - } + $barcode = $options->getBarcodeType(); + return match (true) { + $barcode->is2D() => $this->barcodeContentGenerator->getURLContent($target), + $barcode->is1D() => $this->barcodeContentGenerator->get1DBarcodeContent($target), + $barcode === BarcodeType::NONE => null, + default => throw new InvalidArgumentException('Unknown label type!'), + }; } } diff --git a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php index 588e34b4..5acf98f1 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeContentGenerator.php @@ -48,6 +48,9 @@ use App\Entity\Parts\Storelocation; use InvalidArgumentException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +/** + * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeContentGeneratorTest + */ final class BarcodeContentGenerator { public const PREFIX_MAP = [ @@ -62,11 +65,8 @@ final class BarcodeContentGenerator Storelocation::class => 'location', ]; - private UrlGeneratorInterface $urlGenerator; - - public function __construct(UrlGeneratorInterface $urlGenerator) + public function __construct(private readonly UrlGeneratorInterface $urlGenerator) { - $this->urlGenerator = $urlGenerator; } /** @@ -97,17 +97,17 @@ final class BarcodeContentGenerator private function classToString(array $map, object $target): string { - $class = get_class($target); + $class = $target::class; if (isset($map[$class])) { return $map[$class]; } foreach ($map as $class => $string) { - if (is_a($target, $class)) { + if ($target instanceof $class) { return $string; } } - throw new InvalidArgumentException('Unknown object class '.get_class($target)); + throw new InvalidArgumentException('Unknown object class '.$target::class); } } diff --git a/src/Services/LabelSystem/Barcodes/BarcodeNormalizer.php b/src/Services/LabelSystem/Barcodes/BarcodeNormalizer.php index 4e6d8cbd..a5b6cb5e 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeNormalizer.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeNormalizer.php @@ -43,6 +43,9 @@ namespace App\Services\LabelSystem\Barcodes; use InvalidArgumentException; +/** + * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeNormalizerTest + */ final class BarcodeNormalizer { private const PREFIX_TYPE_MAP = [ diff --git a/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php b/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php index 198cb43b..0eba0ed4 100644 --- a/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php +++ b/src/Services/LabelSystem/Barcodes/BarcodeRedirector.php @@ -47,15 +47,13 @@ use Doctrine\ORM\EntityNotFoundException; use InvalidArgumentException; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +/** + * @see \App\Tests\Services\LabelSystem\Barcodes\BarcodeRedirectorTest + */ final class BarcodeRedirector { - private UrlGeneratorInterface $urlGenerator; - private EntityManagerInterface $em; - - public function __construct(UrlGeneratorInterface $urlGenerator, EntityManagerInterface $entityManager) + public function __construct(private readonly UrlGeneratorInterface $urlGenerator, private readonly EntityManagerInterface $em) { - $this->urlGenerator = $urlGenerator; - $this->em = $entityManager; } /** @@ -76,7 +74,7 @@ final class BarcodeRedirector case 'lot': //Try to determine the part to the given lot $lot = $this->em->find(PartLot::class, $id); - if (null === $lot) { + if (!$lot instanceof PartLot) { throw new EntityNotFoundException(); } diff --git a/src/Services/LabelSystem/LabelExampleElementsGenerator.php b/src/Services/LabelSystem/LabelExampleElementsGenerator.php index 0bc3761a..d7c76c73 100644 --- a/src/Services/LabelSystem/LabelExampleElementsGenerator.php +++ b/src/Services/LabelSystem/LabelExampleElementsGenerator.php @@ -42,6 +42,7 @@ declare(strict_types=1); namespace App\Services\LabelSystem; use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\LabelSystem\LabelSupportedElement; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; @@ -55,18 +56,13 @@ use ReflectionClass; final class LabelExampleElementsGenerator { - public function getElement(string $type): object + public function getElement(LabelSupportedElement $type): object { - switch ($type) { - case 'part': - return $this->getExamplePart(); - case 'part_lot': - return $this->getExamplePartLot(); - case 'storelocation': - return $this->getStorelocation(); - default: - throw new InvalidArgumentException('Unknown $type.'); - } + return match ($type) { + LabelSupportedElement::PART => $this->getExamplePart(), + LabelSupportedElement::PART_LOT => $this->getExamplePartLot(), + LabelSupportedElement::STORELOCATION => $this->getStorelocation(), + }; } public function getExamplePart(): Part @@ -135,6 +131,14 @@ final class LabelExampleElementsGenerator return $user; } + /** + * @template T of AbstractStructuralDBElement + * @param string $class + * @phpstan-param class-string $class + * @return AbstractStructuralDBElement + * @phpstan-return T + * @throws \ReflectionException + */ private function getStructuralData(string $class): AbstractStructuralDBElement { if (!is_a($class, AbstractStructuralDBElement::class, true)) { diff --git a/src/Services/LabelSystem/LabelGenerator.php b/src/Services/LabelSystem/LabelGenerator.php index 4afb20ae..943a4245 100644 --- a/src/Services/LabelSystem/LabelGenerator.php +++ b/src/Services/LabelSystem/LabelGenerator.php @@ -48,27 +48,21 @@ use App\Entity\Parts\Storelocation; use Dompdf\Dompdf; use InvalidArgumentException; +/** + * @see \App\Tests\Services\LabelSystem\LabelGeneratorTest + */ final class LabelGenerator { - public const CLASS_SUPPORT_MAPPING = [ - 'part' => Part::class, - 'part_lot' => PartLot::class, - 'storelocation' => Storelocation::class, - ]; - public const MM_TO_POINTS_FACTOR = 2.83465; - private LabelHTMLGenerator $labelHTMLGenerator; - - public function __construct(LabelHTMLGenerator $labelHTMLGenerator) + public function __construct(private readonly LabelHTMLGenerator $labelHTMLGenerator) { - $this->labelHTMLGenerator = $labelHTMLGenerator; } /** - * @param object|object[] $elements An element or an array of elements for which labels should be generated + * @param object|object[] $elements An element or an array of elements for which labels should be generated */ - public function generateLabel(LabelOptions $options, $elements): string + public function generateLabel(LabelOptions $options, object|array $elements): string { if (!is_array($elements) && !is_object($elements)) { throw new InvalidArgumentException('$element must be an object or an array of objects!'); @@ -89,7 +83,7 @@ final class LabelGenerator $dompdf->loadHtml($this->labelHTMLGenerator->getLabelHTML($options, $elements)); $dompdf->render(); - return $dompdf->output(); + return $dompdf->output() ?? throw new \RuntimeException('Could not generate label!'); } /** @@ -98,11 +92,8 @@ final class LabelGenerator public function supports(LabelOptions $options, object $element): bool { $supported_type = $options->getSupportedElement(); - if (!isset(static::CLASS_SUPPORT_MAPPING[$supported_type])) { - throw new InvalidArgumentException('Supported type name of the Label options not known!'); - } - return is_a($element, static::CLASS_SUPPORT_MAPPING[$supported_type]); + return is_a($element, $supported_type->getEntityClass()); } /** diff --git a/src/Services/LabelSystem/LabelHTMLGenerator.php b/src/Services/LabelSystem/LabelHTMLGenerator.php index f526ac9d..7b6defa6 100644 --- a/src/Services/LabelSystem/LabelHTMLGenerator.php +++ b/src/Services/LabelSystem/LabelHTMLGenerator.php @@ -41,53 +41,38 @@ declare(strict_types=1); namespace App\Services\LabelSystem; +use App\Entity\LabelSystem\LabelProcessMode; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Contracts\NamedElementInterface; use App\Entity\LabelSystem\LabelOptions; use App\Exceptions\TwigModeException; use App\Services\ElementTypeNameGenerator; use InvalidArgumentException; -use Symfony\Component\Security\Core\Security; use Twig\Environment; use Twig\Error\Error; final class LabelHTMLGenerator { - private Environment $twig; - private ElementTypeNameGenerator $elementTypeNameGenerator; - private LabelTextReplacer $replacer; - private BarcodeGenerator $barcodeGenerator; - private SandboxedTwigProvider $sandboxedTwigProvider; - private string $partdb_title; - private Security $security; - - public function __construct(ElementTypeNameGenerator $elementTypeNameGenerator, LabelTextReplacer $replacer, Environment $twig, - BarcodeGenerator $barcodeGenerator, SandboxedTwigProvider $sandboxedTwigProvider, Security $security, string $partdb_title) + public function __construct(private readonly ElementTypeNameGenerator $elementTypeNameGenerator, private readonly LabelTextReplacer $replacer, private readonly Environment $twig, private readonly BarcodeGenerator $barcodeGenerator, private readonly SandboxedTwigProvider $sandboxedTwigProvider, private readonly Security $security, private readonly string $partdb_title) { - $this->twig = $twig; - $this->elementTypeNameGenerator = $elementTypeNameGenerator; - $this->replacer = $replacer; - $this->barcodeGenerator = $barcodeGenerator; - $this->sandboxedTwigProvider = $sandboxedTwigProvider; - $this->security = $security; - $this->partdb_title = $partdb_title; } public function getLabelHTML(LabelOptions $options, array $elements): string { - if (empty($elements)) { + if ($elements === []) { throw new InvalidArgumentException('$elements must not be empty'); } $twig_elements = []; - if ('twig' === $options->getLinesMode()) { + if (LabelProcessMode::TWIG === $options->getProcessMode()) { $sandboxed_twig = $this->sandboxedTwigProvider->getTwig($options); $current_user = $this->security->getUser(); } $page = 1; foreach ($elements as $element) { - if (isset($sandboxed_twig, $current_user) && 'twig' === $options->getLinesMode()) { + if (isset($sandboxed_twig, $current_user) && LabelProcessMode::TWIG === $options->getProcessMode()) { try { $lines = $sandboxed_twig->render( 'lines', diff --git a/src/Services/LabelSystem/LabelProfileDropdownHelper.php b/src/Services/LabelSystem/LabelProfileDropdownHelper.php index 8245c062..d7f1120d 100644 --- a/src/Services/LabelSystem/LabelProfileDropdownHelper.php +++ b/src/Services/LabelSystem/LabelProfileDropdownHelper.php @@ -42,6 +42,7 @@ declare(strict_types=1); namespace App\Services\LabelSystem; use App\Entity\LabelSystem\LabelProfile; +use App\Entity\LabelSystem\LabelSupportedElement; use App\Repository\LabelProfileRepository; use App\Services\UserSystem\UserCacheKeyGenerator; use Doctrine\ORM\EntityManagerInterface; @@ -50,21 +51,24 @@ use Symfony\Contracts\Cache\TagAwareCacheInterface; final class LabelProfileDropdownHelper { - private TagAwareCacheInterface $cache; - private EntityManagerInterface $entityManager; - private UserCacheKeyGenerator $keyGenerator; - - public function __construct(TagAwareCacheInterface $treeCache, EntityManagerInterface $entityManager, UserCacheKeyGenerator $keyGenerator) + public function __construct(private readonly TagAwareCacheInterface $cache, private readonly EntityManagerInterface $entityManager, private readonly UserCacheKeyGenerator $keyGenerator) { - $this->cache = $treeCache; - $this->entityManager = $entityManager; - $this->keyGenerator = $keyGenerator; } - public function getDropdownProfiles(string $type): array + /** + * Return all label profiles for the given supported element type + * @param LabelSupportedElement|string $type + * @return array + */ + public function getDropdownProfiles(LabelSupportedElement|string $type): array { + //Useful for the twig templates, where we use the string representation of the enum + if (is_string($type)) { + $type = LabelSupportedElement::from($type); + } + $secure_class_name = str_replace('\\', '_', LabelProfile::class); - $key = 'profile_dropdown_'.$this->keyGenerator->generateKey().'_'.$secure_class_name.'_'.$type; + $key = 'profile_dropdown_'.$this->keyGenerator->generateKey().'_'.$secure_class_name.'_'.$type->value; /** @var LabelProfileRepository $repo */ $repo = $this->entityManager->getRepository(LabelProfile::class); diff --git a/src/Services/LabelSystem/LabelTextReplacer.php b/src/Services/LabelSystem/LabelTextReplacer.php index 5b94352b..034e243f 100644 --- a/src/Services/LabelSystem/LabelTextReplacer.php +++ b/src/Services/LabelSystem/LabelTextReplacer.php @@ -46,14 +46,12 @@ use App\Services\LabelSystem\PlaceholderProviders\PlaceholderProviderInterface; /** * This service replaces the Placeholders of the user provided lines with the proper informations. * It uses the PlaceholderProviders provided by PlaceholderProviderInterface classes. + * @see \App\Tests\Services\LabelSystem\LabelTextReplacerTest */ final class LabelTextReplacer { - private iterable $providers; - - public function __construct(iterable $providers) + public function __construct(private readonly iterable $providers) { - $this->providers = $providers; } /** @@ -79,21 +77,20 @@ final class LabelTextReplacer } /** - * Replaces all placeholders in the input lines. + * Replaces all placeholders in the input lines. * * @param string $lines The input lines that should be replaced - * @param object $target the object that should be used as source for the informations + * @param object $target the object that should be used as source for the information * - * @return string the Lines with replaced informations + * @return string the Lines with replaced information */ public function replace(string $lines, object $target): string { $patterns = [ - '/(\[\[[A-Z_0-9]+\]\])/' => function ($match) use ($target) { - return $this->handlePlaceholder($match[0], $target); - }, + '/(\[\[[A-Z_0-9]+\]\])/' => fn($match): string => $this->handlePlaceholder($match[0], $target), ]; - return preg_replace_callback_array($patterns, $lines); + return preg_replace_callback_array($patterns, $lines) ?? throw new \RuntimeException('Could not replace placeholders!'); + } } diff --git a/src/Services/LabelSystem/PlaceholderProviders/AbstractDBElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/AbstractDBElementProvider.php index f765cd0c..081b3e91 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/AbstractDBElementProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/AbstractDBElementProvider.php @@ -46,11 +46,8 @@ use App\Services\ElementTypeNameGenerator; final class AbstractDBElementProvider implements PlaceholderProviderInterface { - private ElementTypeNameGenerator $elementTypeNameGenerator; - - public function __construct(ElementTypeNameGenerator $elementTypeNameGenerator) + public function __construct(private readonly ElementTypeNameGenerator $elementTypeNameGenerator) { - $this->elementTypeNameGenerator = $elementTypeNameGenerator; } public function replace(string $placeholder, object $label_target, array $options = []): ?string diff --git a/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php b/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php index 06144831..11824054 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/BarcodeProvider.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\LabelSystem\PlaceholderProviders; +use App\Entity\LabelSystem\BarcodeType; use App\Entity\LabelSystem\LabelOptions; use App\Services\LabelSystem\BarcodeGenerator; use App\Services\LabelSystem\Barcodes\BarcodeContentGenerator; final class BarcodeProvider implements PlaceholderProviderInterface { - private BarcodeGenerator $barcodeGenerator; - private BarcodeContentGenerator $barcodeContentGenerator; - - public function __construct(BarcodeGenerator $barcodeGenerator, BarcodeContentGenerator $barcodeContentGenerator) + public function __construct(private readonly BarcodeGenerator $barcodeGenerator, private readonly BarcodeContentGenerator $barcodeContentGenerator) { - $this->barcodeGenerator = $barcodeGenerator; - $this->barcodeContentGenerator = $barcodeContentGenerator; } public function replace(string $placeholder, object $label_target, array $options = []): ?string @@ -40,7 +38,7 @@ final class BarcodeProvider implements PlaceholderProviderInterface if ('[[1D_CONTENT]]' === $placeholder) { try { return $this->barcodeContentGenerator->get1DBarcodeContent($label_target); - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException) { return 'ERROR!'; } } @@ -48,29 +46,29 @@ final class BarcodeProvider implements PlaceholderProviderInterface if ('[[2D_CONTENT]]' === $placeholder) { try { return $this->barcodeContentGenerator->getURLContent($label_target); - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException) { return 'ERROR!'; } } if ('[[BARCODE_QR]]' === $placeholder) { $label_options = new LabelOptions(); - $label_options->setBarcodeType('qr'); + $label_options->setBarcodeType(BarcodeType::QR); return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target); } if ('[[BARCODE_C39]]' === $placeholder) { $label_options = new LabelOptions(); - $label_options->setBarcodeType('code39'); + $label_options->setBarcodeType(BarcodeType::CODE39); return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target); } if ('[[BARCODE_C128]]' === $placeholder) { $label_options = new LabelOptions(); - $label_options->setBarcodeType('code128'); + $label_options->setBarcodeType(BarcodeType::CODE128); return $this->barcodeGenerator->generateHTMLBarcode($label_options, $label_target); } return null; } -} \ No newline at end of file +} diff --git a/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php index dae87fd4..5a9b2294 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php +++ b/src/Services/LabelSystem/PlaceholderProviders/GlobalProviders.php @@ -41,27 +41,21 @@ declare(strict_types=1); namespace App\Services\LabelSystem\PlaceholderProviders; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\UserSystem\User; use DateTime; use IntlDateFormatter; use Locale; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Security\Core\Security; /** * Provides Placeholders for infos about global infos like Installation name or datetimes. + * @see \App\Tests\Services\LabelSystem\PlaceholderProviders\GlobalProvidersTest */ final class GlobalProviders implements PlaceholderProviderInterface { - private string $partdb_title; - private Security $security; - private UrlGeneratorInterface $url_generator; - - public function __construct(string $partdb_title, Security $security, UrlGeneratorInterface $url_generator) + public function __construct(private readonly string $partdb_title, private readonly Security $security, private readonly UrlGeneratorInterface $url_generator) { - $this->partdb_title = $partdb_title; - $this->security = $security; - $this->url_generator = $url_generator; } public function replace(string $placeholder, object $label_target, array $options = []): ?string diff --git a/src/Services/LabelSystem/PlaceholderProviders/NamedElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/NamedElementProvider.php index fc5fedfe..d8d38120 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/NamedElementProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/NamedElementProvider.php @@ -43,6 +43,9 @@ namespace App\Services\LabelSystem\PlaceholderProviders; use App\Entity\Contracts\NamedElementInterface; +/** + * @see \App\Tests\Services\LabelSystem\PlaceholderProviders\NamedElementProviderTest + */ final class NamedElementProvider implements PlaceholderProviderInterface { public function replace(string $placeholder, object $label_target, array $options = []): ?string diff --git a/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php b/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php index a4fdf32a..fb9447ba 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/PartLotProvider.php @@ -41,21 +41,21 @@ declare(strict_types=1); namespace App\Services\LabelSystem\PlaceholderProviders; +use App\Entity\Parts\Storelocation; +use App\Entity\UserSystem\User; use App\Entity\Parts\PartLot; use App\Services\Formatters\AmountFormatter; use App\Services\LabelSystem\LabelTextReplacer; use IntlDateFormatter; use Locale; +/** + * @see \App\Tests\Services\LabelSystem\PlaceholderProviders\PartLotProviderTest + */ final class PartLotProvider implements PlaceholderProviderInterface { - private LabelTextReplacer $labelTextReplacer; - private AmountFormatter $amountFormatter; - - public function __construct(LabelTextReplacer $labelTextReplacer, AmountFormatter $amountFormatter) + public function __construct(private readonly LabelTextReplacer $labelTextReplacer, private readonly AmountFormatter $amountFormatter) { - $this->labelTextReplacer = $labelTextReplacer; - $this->amountFormatter = $amountFormatter; } public function replace(string $placeholder, object $label_target, array $options = []): ?string @@ -74,7 +74,7 @@ final class PartLotProvider implements PlaceholderProviderInterface } if ('[[EXPIRATION_DATE]]' === $placeholder) { - if (null === $label_target->getExpirationDate()) { + if (!$label_target->getExpirationDate() instanceof \DateTimeInterface) { return ''; } $formatter = IntlDateFormatter::create( @@ -95,19 +95,19 @@ final class PartLotProvider implements PlaceholderProviderInterface } if ('[[LOCATION]]' === $placeholder) { - return $label_target->getStorageLocation() ? $label_target->getStorageLocation()->getName() : ''; + return $label_target->getStorageLocation() instanceof Storelocation ? $label_target->getStorageLocation()->getName() : ''; } if ('[[LOCATION_FULL]]' === $placeholder) { - return $label_target->getStorageLocation() ? $label_target->getStorageLocation()->getFullPath() : ''; + return $label_target->getStorageLocation() instanceof Storelocation ? $label_target->getStorageLocation()->getFullPath() : ''; } if ('[[OWNER]]' === $placeholder) { - return $label_target->getOwner() ? $label_target->getOwner()->getFullName() : ''; + return $label_target->getOwner() instanceof User ? $label_target->getOwner()->getFullName() : ''; } if ('[[OWNER_USERNAME]]' === $placeholder) { - return $label_target->getOwner() ? $label_target->getOwner()->getUsername() : ''; + return $label_target->getOwner() instanceof User ? $label_target->getOwner()->getUsername() : ''; } return $this->labelTextReplacer->handlePlaceholder($placeholder, $label_target->getPart()); diff --git a/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php index 48e547f6..4174c623 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/PartProvider.php @@ -41,20 +41,21 @@ declare(strict_types=1); namespace App\Services\LabelSystem\PlaceholderProviders; +use App\Entity\Parts\Category; +use App\Entity\Parts\Manufacturer; +use App\Entity\Parts\Footprint; use App\Entity\Parts\Part; use App\Services\Formatters\SIFormatter; use Parsedown; use Symfony\Contracts\Translation\TranslatorInterface; +/** + * @see \App\Tests\Services\LabelSystem\PlaceholderProviders\PartProviderTest + */ final class PartProvider implements PlaceholderProviderInterface { - private SIFormatter $siFormatter; - private TranslatorInterface $translator; - - public function __construct(SIFormatter $SIFormatter, TranslatorInterface $translator) + public function __construct(private readonly SIFormatter $siFormatter, private readonly TranslatorInterface $translator) { - $this->siFormatter = $SIFormatter; - $this->translator = $translator; } public function replace(string $placeholder, object $part, array $options = []): ?string @@ -64,27 +65,27 @@ final class PartProvider implements PlaceholderProviderInterface } if ('[[CATEGORY]]' === $placeholder) { - return $part->getCategory() ? $part->getCategory()->getName() : ''; + return $part->getCategory() instanceof Category ? $part->getCategory()->getName() : ''; } if ('[[CATEGORY_FULL]]' === $placeholder) { - return $part->getCategory() ? $part->getCategory()->getFullPath() : ''; + return $part->getCategory() instanceof Category ? $part->getCategory()->getFullPath() : ''; } if ('[[MANUFACTURER]]' === $placeholder) { - return $part->getManufacturer() ? $part->getManufacturer()->getName() : ''; + return $part->getManufacturer() instanceof Manufacturer ? $part->getManufacturer()->getName() : ''; } if ('[[MANUFACTURER_FULL]]' === $placeholder) { - return $part->getManufacturer() ? $part->getManufacturer()->getFullPath() : ''; + return $part->getManufacturer() instanceof Manufacturer ? $part->getManufacturer()->getFullPath() : ''; } if ('[[FOOTPRINT]]' === $placeholder) { - return $part->getFootprint() ? $part->getFootprint()->getName() : ''; + return $part->getFootprint() instanceof Footprint ? $part->getFootprint()->getName() : ''; } if ('[[FOOTPRINT_FULL]]' === $placeholder) { - return $part->getFootprint() ? $part->getFootprint()->getFullPath() : ''; + return $part->getFootprint() instanceof Footprint ? $part->getFootprint()->getFullPath() : ''; } if ('[[MASS]]' === $placeholder) { @@ -114,7 +115,7 @@ final class PartProvider implements PlaceholderProviderInterface } if ('[[DESCRIPTION_T]]' === $placeholder) { - return strip_tags($parsedown->line($part->getDescription())); + return strip_tags((string) $parsedown->line($part->getDescription())); } if ('[[COMMENT]]' === $placeholder) { @@ -122,7 +123,7 @@ final class PartProvider implements PlaceholderProviderInterface } if ('[[COMMENT_T]]' === $placeholder) { - return strip_tags($parsedown->line($part->getComment())); + return strip_tags((string) $parsedown->line($part->getComment())); } return null; diff --git a/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php b/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php index aac7f985..95654eca 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/StorelocationProvider.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\LabelSystem\PlaceholderProviders; +use App\Entity\UserSystem\User; use App\Entity\Parts\Storelocation; class StorelocationProvider implements PlaceholderProviderInterface @@ -28,14 +31,14 @@ class StorelocationProvider implements PlaceholderProviderInterface { if ($label_target instanceof Storelocation) { if ('[[OWNER]]' === $placeholder) { - return $label_target->getOwner() ? $label_target->getOwner()->getFullName() : ''; + return $label_target->getOwner() instanceof User ? $label_target->getOwner()->getFullName() : ''; } if ('[[OWNER_USERNAME]]' === $placeholder) { - return $label_target->getOwner() ? $label_target->getOwner()->getUsername() : ''; + return $label_target->getOwner() instanceof User ? $label_target->getOwner()->getUsername() : ''; } } return null; } -} \ No newline at end of file +} diff --git a/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php index f4aebd8a..ca8088da 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/StructuralDBElementProvider.php @@ -58,10 +58,10 @@ final class StructuralDBElementProvider implements PlaceholderProviderInterface return $label_target->getFullPath(); } if ('[[PARENT]]' === $placeholder) { - return $label_target->getParent() ? $label_target->getParent()->getName() : ''; + return $label_target->getParent() instanceof AbstractStructuralDBElement ? $label_target->getParent()->getName() : ''; } if ('[[PARENT_FULL_PATH]]' === $placeholder) { - return $label_target->getParent() ? $label_target->getParent()->getFullPath() : ''; + return $label_target->getParent() instanceof AbstractStructuralDBElement ? $label_target->getParent()->getFullPath() : ''; } } diff --git a/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php b/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php index ef5967b2..8588e133 100644 --- a/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php +++ b/src/Services/LabelSystem/PlaceholderProviders/TimestampableElementProvider.php @@ -46,6 +46,9 @@ use DateTime; use IntlDateFormatter; use Locale; +/** + * @see \App\Tests\Services\LabelSystem\PlaceholderProviders\TimestampableElementProviderTest + */ final class TimestampableElementProvider implements PlaceholderProviderInterface { public function replace(string $placeholder, object $label_target, array $options = []): ?string diff --git a/src/Services/LabelSystem/SandboxedTwigProvider.php b/src/Services/LabelSystem/SandboxedTwigProvider.php index 66488fca..a0426cd1 100644 --- a/src/Services/LabelSystem/SandboxedTwigProvider.php +++ b/src/Services/LabelSystem/SandboxedTwigProvider.php @@ -49,6 +49,7 @@ use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Contracts\NamedElementInterface; use App\Entity\Contracts\TimeStampableInterface; use App\Entity\LabelSystem\LabelOptions; +use App\Entity\LabelSystem\LabelProcessMode; use App\Entity\Parameters\AbstractParameter; use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\Part; @@ -68,6 +69,9 @@ use Twig\Extra\Intl\IntlExtension; use Twig\Loader\ArrayLoader; use Twig\Sandbox\SecurityPolicyInterface; +/** + * @see \App\Tests\Services\LabelSystem\SandboxedTwigProviderTest + */ final class SandboxedTwigProvider { private const ALLOWED_TAGS = ['apply', 'autoescape', 'do', 'for', 'if', 'set', 'verbatim', 'with']; @@ -114,16 +118,13 @@ final class SandboxedTwigProvider ]; private const ALLOWED_PROPERTIES = []; - private FormatExtension $appExtension; - - public function __construct(FormatExtension $appExtension) + public function __construct(private readonly FormatExtension $appExtension) { - $this->appExtension = $appExtension; } public function getTwig(LabelOptions $options): Environment { - if ('twig' !== $options->getLinesMode()) { + if (LabelProcessMode::TWIG !== $options->getProcessMode()) { throw new InvalidArgumentException('The LabelOptions must explicitly allow twig via lines_mode = "twig"!'); } diff --git a/src/Services/LogSystem/EventCommentHelper.php b/src/Services/LogSystem/EventCommentHelper.php index 4afcf04d..45e95b2c 100644 --- a/src/Services/LogSystem/EventCommentHelper.php +++ b/src/Services/LogSystem/EventCommentHelper.php @@ -41,15 +41,17 @@ declare(strict_types=1); namespace App\Services\LogSystem; +/** + * @see \App\Tests\Services\LogSystem\EventCommentHelperTest + */ class EventCommentHelper { protected const MAX_MESSAGE_LENGTH = 255; - protected ?string $message; + protected ?string $message = null; public function __construct() { - $this->message = null; } /** @@ -60,11 +62,7 @@ class EventCommentHelper public function setMessage(?string $message): void { //Restrict the length of the string - if ($message) { - $this->message = mb_strimwidth($message, 0, self::MAX_MESSAGE_LENGTH, '...'); - } else { - $this->message = null; - } + $this->message = $message ? mb_strimwidth($message, 0, self::MAX_MESSAGE_LENGTH, '...') : null; } /** diff --git a/src/Services/LogSystem/EventCommentNeededHelper.php b/src/Services/LogSystem/EventCommentNeededHelper.php index 7305b304..8440f199 100644 --- a/src/Services/LogSystem/EventCommentNeededHelper.php +++ b/src/Services/LogSystem/EventCommentNeededHelper.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\LogSystem; /** * This service is used to check if a log change comment is needed for a given operation type. * It is configured using the "enforce_change_comments_for" config parameter. + * @see \App\Tests\Services\LogSystem\EventCommentNeededHelperTest */ class EventCommentNeededHelper { - protected array $enforce_change_comments_for; - - public const VALID_OPERATION_TYPES = [ + final public const VALID_OPERATION_TYPES = [ 'part_edit', 'part_create', 'part_delete', @@ -38,15 +39,12 @@ class EventCommentNeededHelper 'datastructure_delete', ]; - public function __construct(array $enforce_change_comments_for) + public function __construct(protected array $enforce_change_comments_for) { - $this->enforce_change_comments_for = $enforce_change_comments_for; } /** * Checks if a log change comment is needed for the given operation type - * @param string $comment_type - * @return bool */ public function isCommentNeeded(string $comment_type): bool { @@ -57,4 +55,4 @@ class EventCommentNeededHelper return in_array($comment_type, $this->enforce_change_comments_for, true); } -} \ No newline at end of file +} diff --git a/src/Services/LogSystem/EventLogger.php b/src/Services/LogSystem/EventLogger.php index 0216cc3a..11147de6 100644 --- a/src/Services/LogSystem/EventLogger.php +++ b/src/Services/LogSystem/EventLogger.php @@ -22,30 +22,24 @@ declare(strict_types=1); namespace App\Services\LogSystem; +use App\Entity\LogSystem\LogLevel; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\User\UserInterface; use App\Entity\LogSystem\AbstractLogEntry; use App\Entity\UserSystem\User; use App\Services\Misc\ConsoleInfoHelper; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\Security\Core\Security; +/** + * @see \App\Tests\Services\LogSystem\EventLoggerTest + */ class EventLogger { - protected int $minimum_log_level; - protected array $blacklist; - protected array $whitelist; - protected EntityManagerInterface $em; - protected Security $security; - protected ConsoleInfoHelper $console_info_helper; + protected LogLevel $minimum_log_level; - public function __construct(int $minimum_log_level, array $blacklist, array $whitelist, EntityManagerInterface $em, - Security $security, ConsoleInfoHelper $console_info_helper) + public function __construct(int $minimum_log_level, protected array $blacklist, protected array $whitelist, protected EntityManagerInterface $em, protected Security $security, protected ConsoleInfoHelper $console_info_helper) { - $this->minimum_log_level = $minimum_log_level; - $this->blacklist = $blacklist; - $this->whitelist = $whitelist; - $this->em = $em; - $this->security = $security; - $this->console_info_helper = $console_info_helper; + $this->minimum_log_level = LogLevel::tryFrom($minimum_log_level); } /** @@ -58,14 +52,14 @@ class EventLogger { $user = $this->security->getUser(); //If the user is not specified explicitly, set it to the current user - if ((null === $user || $user instanceof User) && null === $logEntry->getUser()) { - if (null === $user) { + if ((!$user instanceof UserInterface || $user instanceof User) && !$logEntry->getUser() instanceof User) { + if (!$user instanceof User) { $repo = $this->em->getRepository(User::class); $user = $repo->getAnonymousUser(); } //If no anonymous user is available skip the log (needed for data fixtures) - if (null === $user) { + if (!$user instanceof User) { return false; } $logEntry->setUser($user); @@ -88,15 +82,13 @@ class EventLogger /** * Same as log(), but this function can be safely called from within the onFlush() doctrine event, as it * updated the changesets of the unit of work. - * @param AbstractLogEntry $logEntry - * @return bool */ public function logFromOnFlush(AbstractLogEntry $logEntry): bool { if ($this->log($logEntry)) { $uow = $this->em->getUnitOfWork(); //As we call it from onFlush, we have to recompute the changeset here, according to https://www.doctrine-project.org/projects/doctrine-orm/en/2.14/reference/events.html#reference-events-on-flush - $uow->computeChangeSet($this->em->getClassMetadata(get_class($logEntry)), $logEntry); + $uow->computeChangeSet($this->em->getClassMetadata($logEntry::class), $logEntry); return true; } @@ -120,32 +112,27 @@ class EventLogger public function shouldBeAdded( AbstractLogEntry $logEntry, - ?int $minimum_log_level = null, + ?LogLevel $minimum_log_level = null, ?array $blacklist = null, ?array $whitelist = null ): bool { //Apply the global settings, if nothing was specified - $minimum_log_level = $minimum_log_level ?? $this->minimum_log_level; - $blacklist = $blacklist ?? $this->blacklist; - $whitelist = $whitelist ?? $this->whitelist; + $minimum_log_level ??= $this->minimum_log_level; + $blacklist ??= $this->blacklist; + $whitelist ??= $this->whitelist; //Don't add the entry if it does not reach the minimum level - if ($logEntry->getLevel() > $minimum_log_level) { + if ($logEntry->getLevel()->lessImportThan($minimum_log_level)) { return false; } //Check if the event type is blacklisted - if (!empty($blacklist) && $this->isObjectClassInArray($logEntry, $blacklist)) { + if ($blacklist !== [] && $this->isObjectClassInArray($logEntry, $blacklist)) { return false; } - //Check for whitelisting - if (!empty($whitelist) && !$this->isObjectClassInArray($logEntry, $whitelist)) { - return false; - } - // By default, all things should be added - return true; + return !($whitelist !== [] && !$this->isObjectClassInArray($logEntry, $whitelist)); } /** @@ -157,13 +144,13 @@ class EventLogger protected function isObjectClassInArray(object $object, array $classes): bool { //Check if the class is directly in the classes array - if (in_array(get_class($object), $classes, true)) { + if (in_array($object::class, $classes, true)) { return true; } //Iterate over all classes and check for inheritance foreach ($classes as $class) { - if (is_a($object, $class)) { + if ($object instanceof $class) { return true; } } diff --git a/src/Services/LogSystem/EventUndoHelper.php b/src/Services/LogSystem/EventUndoHelper.php index f2328cbc..ed3bc363 100644 --- a/src/Services/LogSystem/EventUndoHelper.php +++ b/src/Services/LogSystem/EventUndoHelper.php @@ -46,29 +46,19 @@ use InvalidArgumentException; class EventUndoHelper { - public const MODE_UNDO = 'undo'; - public const MODE_REVERT = 'revert'; - - protected const ALLOWED_MODES = [self::MODE_REVERT, self::MODE_UNDO]; - - protected ?AbstractLogEntry $undone_event; - protected string $mode; + protected ?AbstractLogEntry $undone_event = null; + protected EventUndoMode $mode = EventUndoMode::UNDO; public function __construct() { - $this->undone_event = null; - $this->mode = self::MODE_UNDO; } - public function setMode(string $mode): void + public function setMode(EventUndoMode $mode): void { - if (!in_array($mode, self::ALLOWED_MODES, true)) { - throw new InvalidArgumentException('Invalid mode passed!'); - } $this->mode = $mode; } - public function getMode(): string + public function getMode(): EventUndoMode { return $this->mode; } diff --git a/src/Services/LogSystem/EventUndoMode.php b/src/Services/LogSystem/EventUndoMode.php new file mode 100644 index 00000000..51ad664e --- /dev/null +++ b/src/Services/LogSystem/EventUndoMode.php @@ -0,0 +1,46 @@ +. + */ + +namespace App\Services\LogSystem; + +use InvalidArgumentException; + +enum EventUndoMode: string +{ + case UNDO = 'undo'; + case REVERT = 'revert'; + + public function toExtraInt(): int + { + return match ($this) { + self::UNDO => 1, + self::REVERT => 2, + }; + } + + public static function fromExtraInt(int $int): self + { + return match ($int) { + 1 => self::UNDO, + 2 => self::REVERT, + default => throw new InvalidArgumentException('Invalid int ' . (string) $int . ' for EventUndoMode'), + }; + } +} diff --git a/src/Services/LogSystem/HistoryHelper.php b/src/Services/LogSystem/HistoryHelper.php index e1638f41..f4752b6f 100644 --- a/src/Services/LogSystem/HistoryHelper.php +++ b/src/Services/LogSystem/HistoryHelper.php @@ -55,10 +55,10 @@ class HistoryHelper } /** - * Returns an array containing all elements that are associated with the argument. - * The returned array contains the given element. + * Returns an array containing all elements that are associated with the argument. + * The returned array contains the given element. * - * @psalm-return array + * @return AbstractDBElement[] */ public function getAssociatedElements(AbstractDBElement $element): array { diff --git a/src/Services/LogSystem/LogDataFormatter.php b/src/Services/LogSystem/LogDataFormatter.php index 1980c029..f15fcdc6 100644 --- a/src/Services/LogSystem/LogDataFormatter.php +++ b/src/Services/LogSystem/LogDataFormatter.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\LogSystem; use App\Entity\LogSystem\AbstractLogEntry; @@ -29,25 +31,14 @@ class LogDataFormatter { private const STRING_MAX_LENGTH = 1024; - private TranslatorInterface $translator; - private EntityManagerInterface $entityManager; - private ElementTypeNameGenerator $elementTypeNameGenerator; - - public function __construct(TranslatorInterface $translator, EntityManagerInterface $entityManager, ElementTypeNameGenerator $elementTypeNameGenerator) + public function __construct(private readonly TranslatorInterface $translator, private readonly EntityManagerInterface $entityManager, private readonly ElementTypeNameGenerator $elementTypeNameGenerator) { - $this->translator = $translator; - $this->entityManager = $entityManager; - $this->elementTypeNameGenerator = $elementTypeNameGenerator; } /** * Formats the given data of a log entry as HTML - * @param mixed $data - * @param AbstractLogEntry $logEntry - * @param string $fieldName - * @return string */ - public function formatData($data, AbstractLogEntry $logEntry, string $fieldName): string + public function formatData(mixed $data, AbstractLogEntry $logEntry, string $fieldName): string { if (is_string($data)) { $tmp = '"' . mb_strimwidth(htmlspecialchars($data), 0, self::STRING_MAX_LENGTH, ) . '"'; @@ -55,9 +46,8 @@ class LogDataFormatter //Show special characters and line breaks $tmp = preg_replace('/\n/', '\\n
', $tmp); $tmp = preg_replace('/\r/', '\\r', $tmp); - $tmp = preg_replace('/\t/', '\\t', $tmp); - return $tmp; + return preg_replace('/\t/', '\\t', $tmp); } if (is_bool($data)) { @@ -126,7 +116,7 @@ class LogDataFormatter } - } catch (\InvalidArgumentException|\ReflectionException $exception) { + } catch (\InvalidArgumentException|\ReflectionException) { return 'unknown target class: ' . $id; } } @@ -147,7 +137,7 @@ class LogDataFormatter try { $dateTime = new \DateTime($date, new \DateTimeZone($timezone)); - } catch (\Exception $exception) { + } catch (\Exception) { return 'unknown DateTime format'; } @@ -160,4 +150,4 @@ class LogDataFormatter { return $data ? $this->translator->trans('true') : $this->translator->trans('false'); } -} \ No newline at end of file +} diff --git a/src/Services/LogSystem/LogDiffFormatter.php b/src/Services/LogSystem/LogDiffFormatter.php index 92b6d814..8b165d5a 100644 --- a/src/Services/LogSystem/LogDiffFormatter.php +++ b/src/Services/LogSystem/LogDiffFormatter.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\LogSystem; use Jfcherng\Diff\DiffHelper; @@ -29,7 +31,6 @@ class LogDiffFormatter * If the diff is not possible, an empty string is returned. * @param $old_data * @param $new_data - * @return string */ public function formatDiff($old_data, $new_data): string { @@ -56,7 +57,11 @@ class LogDiffFormatter ]); } - private function diffNumeric($old_data, $new_data): string + /** + * @param numeric $old_data + * @param numeric $new_data + */ + private function diffNumeric(int|float|string $old_data, int|float|string $new_data): string { if ((!is_numeric($old_data)) || (!is_numeric($new_data))) { throw new \InvalidArgumentException('The given data is not numeric.'); @@ -67,10 +72,10 @@ class LogDiffFormatter //Positive difference if ($difference > 0) { return sprintf('+%s', $difference); - } else if ($difference < 0) { + } elseif ($difference < 0) { return sprintf('%s', $difference); } else { return sprintf('%s', $difference); } } -} \ No newline at end of file +} diff --git a/src/Services/LogSystem/LogEntryExtraFormatter.php b/src/Services/LogSystem/LogEntryExtraFormatter.php index 8ec328d0..a1fd4c9b 100644 --- a/src/Services/LogSystem/LogEntryExtraFormatter.php +++ b/src/Services/LogSystem/LogEntryExtraFormatter.php @@ -33,6 +33,7 @@ use App\Entity\LogSystem\ElementEditedLogEntry; use App\Entity\LogSystem\ExceptionLogEntry; use App\Entity\LogSystem\LegacyInstockChangedLogEntry; use App\Entity\LogSystem\PartStockChangedLogEntry; +use App\Entity\LogSystem\PartStockChangeType; use App\Entity\LogSystem\SecurityEventLogEntry; use App\Entity\LogSystem\UserLoginLogEntry; use App\Entity\LogSystem\UserLogoutLogEntry; @@ -48,13 +49,9 @@ class LogEntryExtraFormatter { protected const CONSOLE_SEARCH = ['', '', '', '', '']; protected const CONSOLE_REPLACE = ['→', '', '', '', '']; - protected TranslatorInterface $translator; - protected ElementTypeNameGenerator $elementTypeNameGenerator; - public function __construct(TranslatorInterface $translator, ElementTypeNameGenerator $elementTypeNameGenerator) + public function __construct(protected TranslatorInterface $translator, protected ElementTypeNameGenerator $elementTypeNameGenerator) { - $this->translator = $translator; - $this->elementTypeNameGenerator = $elementTypeNameGenerator; } /** @@ -72,7 +69,7 @@ class LogEntryExtraFormatter $str .= ''.$this->translator->trans($key).': '; } $str .= $value; - if (!empty($str)) { + if ($str !== '') { $tmp[] = $str; } } @@ -95,7 +92,7 @@ class LogEntryExtraFormatter $str .= ''.$this->translator->trans($key).': '; } $str .= $value; - if (!empty($str)) { + if ($str !== '') { $tmp[] = $str; } } @@ -130,9 +127,9 @@ class LogEntryExtraFormatter } if (($context instanceof LogWithEventUndoInterface) && $context->isUndoEvent()) { - if ('undo' === $context->getUndoMode()) { + if (EventUndoMode::UNDO === $context->getUndoMode()) { $array['log.undo_mode.undo'] = '#' . $context->getUndoEventID(); - } elseif ('revert' === $context->getUndoMode()) { + } elseif (EventUndoMode::REVERT === $context->getUndoMode()) { $array['log.undo_mode.revert'] = '#' . $context->getUndoEventID(); } } @@ -163,7 +160,7 @@ class LogEntryExtraFormatter '%s %s (%s)', $context->getOldInstock(), $context->getNewInstock(), - (!$context->isWithdrawal() ? '+' : '-').$context->getDifference(true) + ($context->isWithdrawal() ? '-' : '+').$context->getDifference(true) ); $array['log.instock_changed.comment'] = htmlspecialchars($context->getComment()); } @@ -188,10 +185,10 @@ class LogEntryExtraFormatter $context->getNewStock(), ($context->getNewStock() > $context->getOldStock() ? '+' : '-'). $context->getChangeAmount(), ); - if (!empty($context->getComment())) { + if ($context->getComment() !== '') { $array['log.part_stock_changed.comment'] = htmlspecialchars($context->getComment()); } - if ($context->getInstockChangeType() === PartStockChangedLogEntry::TYPE_MOVE) { + if ($context->getInstockChangeType() === PartStockChangeType::MOVE) { $array['log.part_stock_changed.move_target'] = htmlspecialchars($this->elementTypeNameGenerator->getLocalizedTypeLabel(PartLot::class)) .' ' . $context->getMoveToTargetID(); diff --git a/src/Services/LogSystem/LogLevelHelper.php b/src/Services/LogSystem/LogLevelHelper.php index 926531da..5cc13db8 100644 --- a/src/Services/LogSystem/LogLevelHelper.php +++ b/src/Services/LogSystem/LogLevelHelper.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\LogSystem; use App\Entity\LogSystem\AbstractLogEntry; @@ -29,30 +31,20 @@ class LogLevelHelper * Returns the FontAwesome icon class for the given log level. * This returns just the specific icon class (so 'fa-info' for example). * @param string $logLevel The string representation of the log level (one of the LogLevel::* constants) - * @return string */ public function logLevelToIconClass(string $logLevel): string { - switch ($logLevel) { - case LogLevel::DEBUG: - return 'fa-bug'; - case LogLevel::INFO: - return 'fa-info'; - case LogLevel::NOTICE: - return 'fa-flag'; - case LogLevel::WARNING: - return 'fa-exclamation-circle'; - case LogLevel::ERROR: - return 'fa-exclamation-triangle'; - case LogLevel::CRITICAL: - return 'fa-bolt'; - case LogLevel::ALERT: - return 'fa-radiation'; - case LogLevel::EMERGENCY: - return 'fa-skull-crossbones'; - default: - return 'fa-question-circle'; - } + return match ($logLevel) { + LogLevel::DEBUG => 'fa-bug', + LogLevel::INFO => 'fa-info', + LogLevel::NOTICE => 'fa-flag', + LogLevel::WARNING => 'fa-exclamation-circle', + LogLevel::ERROR => 'fa-exclamation-triangle', + LogLevel::CRITICAL => 'fa-bolt', + LogLevel::ALERT => 'fa-radiation', + LogLevel::EMERGENCY => 'fa-skull-crossbones', + default => 'fa-question-circle', + }; } /** @@ -63,18 +55,11 @@ class LogLevelHelper public function logLevelToTableColorClass(string $logLevel): string { - switch ($logLevel) { - case LogLevel::EMERGENCY: - case LogLevel::ALERT: - case LogLevel::CRITICAL: - case LogLevel::ERROR: - return 'table-danger'; - case LogLevel::WARNING: - return 'table-warning'; - case LogLevel::NOTICE: - return 'table-info'; - default: - return ''; - } + return match ($logLevel) { + LogLevel::EMERGENCY, LogLevel::ALERT, LogLevel::CRITICAL, LogLevel::ERROR => 'table-danger', + LogLevel::WARNING => 'table-warning', + LogLevel::NOTICE => 'table-info', + default => '', + }; } -} \ No newline at end of file +} diff --git a/src/Services/LogSystem/LogTargetHelper.php b/src/Services/LogSystem/LogTargetHelper.php index 63946a0f..0a10a023 100644 --- a/src/Services/LogSystem/LogTargetHelper.php +++ b/src/Services/LogSystem/LogTargetHelper.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\LogSystem; use App\Entity\Attachments\Attachment; @@ -40,21 +42,12 @@ use Symfony\Contracts\Translation\TranslatorInterface; class LogTargetHelper { - protected EntityManagerInterface $em; protected LogEntryRepository $entryRepository; - protected EntityURLGenerator $entityURLGenerator; - protected ElementTypeNameGenerator $elementTypeNameGenerator; - protected TranslatorInterface $translator; - public function __construct(EntityManagerInterface $entityManager, EntityURLGenerator $entityURLGenerator, - ElementTypeNameGenerator $elementTypeNameGenerator, TranslatorInterface $translator) + public function __construct(protected EntityManagerInterface $em, protected EntityURLGenerator $entityURLGenerator, + protected ElementTypeNameGenerator $elementTypeNameGenerator, protected TranslatorInterface $translator) { - $this->em = $entityManager; - $this->entryRepository = $entityManager->getRepository(AbstractLogEntry::class); - - $this->entityURLGenerator = $entityURLGenerator; - $this->elementTypeNameGenerator = $elementTypeNameGenerator; - $this->translator = $translator; + $this->entryRepository = $em->getRepository(AbstractLogEntry::class); } private function configureOptions(OptionsResolver $resolver): self @@ -79,10 +72,10 @@ class LogTargetHelper $target = $this->entryRepository->getTargetElement($context); //If the target is null and the context has a target, that means that the target was deleted. Show it that way. - if ($target === null) { + if (!$target instanceof AbstractDBElement) { if ($context->hasTarget()) { return $this->elementTypeNameGenerator->formatElementDeletedHTML($context->getTargetClass(), - $context->getTargetId()); + $context->getTargetID()); } //If no target is set, we can't do anything return ''; @@ -91,4 +84,4 @@ class LogTargetHelper //Otherwise we can return a label for the target return $this->elementTypeNameGenerator->formatLabelHTMLForEntity($target, $options['show_associated']); } -} \ No newline at end of file +} diff --git a/src/Services/LogSystem/TimeTravel.php b/src/Services/LogSystem/TimeTravel.php index daeed7ea..cb612907 100644 --- a/src/Services/LogSystem/TimeTravel.php +++ b/src/Services/LogSystem/TimeTravel.php @@ -43,12 +43,10 @@ use ReflectionClass; class TimeTravel { - protected EntityManagerInterface $em; protected LogEntryRepository $repo; - public function __construct(EntityManagerInterface $em) + public function __construct(protected EntityManagerInterface $em) { - $this->em = $em; $this->repo = $em->getRepository(AbstractLogEntry::class); } @@ -81,7 +79,7 @@ class TimeTravel * * @throws Exception */ - public function revertEntityToTimestamp(AbstractDBElement $element, DateTime $timestamp, array $reverted_elements = []): void + public function revertEntityToTimestamp(AbstractDBElement $element, \DateTimeInterface $timestamp, array $reverted_elements = []): void { if (!$element instanceof TimeStampableInterface) { throw new InvalidArgumentException('$element must have a Timestamp!'); @@ -126,7 +124,7 @@ class TimeTravel } // Revert any of the associated elements - $metadata = $this->em->getClassMetadata(get_class($element)); + $metadata = $this->em->getClassMetadata($element::class); $associations = $metadata->getAssociationMappings(); foreach ($associations as $field => $mapping) { if ( @@ -148,10 +146,10 @@ class TimeTravel } elseif ( //Revert *_TO_MANY associations (collection properties) (ClassMetadataInfo::MANY_TO_MANY === $mapping['type'] || ClassMetadataInfo::ONE_TO_MANY === $mapping['type']) - && false === $mapping['isOwningSide'] + && !$mapping['isOwningSide'] ) { $target_elements = $this->getField($element, $field); - if (null === $target_elements || count($target_elements) > 10) { + if (null === $target_elements || (is_countable($target_elements) ? count($target_elements) : 0) > 10) { continue; } foreach ($target_elements as $target_element) { @@ -200,7 +198,7 @@ class TimeTravel if (!$element instanceof TimeStampableInterface) { return; } - $metadata = $this->em->getClassMetadata(get_class($element)); + $metadata = $this->em->getClassMetadata($element::class); $old_data = $logEntry->getOldData(); foreach ($old_data as $field => $data) { @@ -230,23 +228,21 @@ class TimeTravel $this->setField($element, 'lastModified', $logEntry->getTimestamp()); } - protected function getField(AbstractDBElement $element, string $field) + protected function getField(AbstractDBElement $element, string $field): mixed { - $reflection = new ReflectionClass(get_class($element)); + $reflection = new ReflectionClass($element::class); $property = $reflection->getProperty($field); - $property->setAccessible(true); return $property->getValue($element); } /** - * @param DateTime|int|null $new_value + * @param int|null|object $new_value */ - protected function setField(AbstractDBElement $element, string $field, $new_value): void + protected function setField(AbstractDBElement $element, string $field, mixed $new_value): void { - $reflection = new ReflectionClass(get_class($element)); + $reflection = new ReflectionClass($element::class); $property = $reflection->getProperty($field); - $property->setAccessible(true); $property->setValue($element, $new_value); } diff --git a/src/Services/Misc/ConsoleInfoHelper.php b/src/Services/Misc/ConsoleInfoHelper.php index 8aea004e..f529f74a 100644 --- a/src/Services/Misc/ConsoleInfoHelper.php +++ b/src/Services/Misc/ConsoleInfoHelper.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\Misc; class ConsoleInfoHelper @@ -47,17 +49,7 @@ class ConsoleInfoHelper return $user['name']; } - //Try to retrieve the name via the environment variable Username (Windows) - if (isset($_SERVER['USERNAME'])) { - return $_SERVER['USERNAME']; - } - - //Try to retrieve the name via the environment variable USER (Linux) - if (isset($_SERVER['USER'])) { - return $_SERVER['USER']; - } - //Otherwise we can't determine the username - return null; + return $_SERVER['USERNAME'] ?? $_SERVER['USER'] ?? null; } -} \ No newline at end of file +} diff --git a/src/Services/Misc/DBInfoHelper.php b/src/Services/Misc/DBInfoHelper.php index 896c0637..29440d26 100644 --- a/src/Services/Misc/DBInfoHelper.php +++ b/src/Services/Misc/DBInfoHelper.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\Misc; +use Doctrine\DBAL\Exception; use Doctrine\DBAL\Connection; use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; @@ -31,11 +34,9 @@ use Doctrine\ORM\EntityManagerInterface; class DBInfoHelper { protected Connection $connection; - protected EntityManagerInterface $entityManager; - public function __construct(EntityManagerInterface $entityManager) + public function __construct(protected EntityManagerInterface $entityManager) { - $this->entityManager = $entityManager; $this->connection = $entityManager->getConnection(); } @@ -58,8 +59,7 @@ class DBInfoHelper /** * Returns the database version of the used database. - * @return string|null - * @throws \Doctrine\DBAL\Exception + * @throws Exception */ public function getDatabaseVersion(): ?string { @@ -77,22 +77,22 @@ class DBInfoHelper /** * Returns the database size in bytes. * @return int|null The database size in bytes or null if unknown - * @throws \Doctrine\DBAL\Exception + * @throws Exception */ public function getDatabaseSize(): ?int { if ($this->connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) { try { - return $this->connection->fetchOne('SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema = DATABASE()'); - } catch (\Doctrine\DBAL\Exception $e) { + return (int) $this->connection->fetchOne('SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema = DATABASE()'); + } catch (Exception) { return null; } } if ($this->connection->getDatabasePlatform() instanceof SqlitePlatform) { try { - return $this->connection->fetchOne('SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();'); - } catch (\Doctrine\DBAL\Exception $e) { + return (int) $this->connection->fetchOne('SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();'); + } catch (Exception) { return null; } } @@ -102,7 +102,6 @@ class DBInfoHelper /** * Returns the name of the database. - * @return string|null */ public function getDatabaseName(): ?string { @@ -111,14 +110,13 @@ class DBInfoHelper /** * Returns the name of the database user. - * @return string|null */ public function getDatabaseUsername(): ?string { if ($this->connection->getDatabasePlatform() instanceof AbstractMySQLPlatform) { try { return $this->connection->fetchOne('SELECT USER()'); - } catch (\Doctrine\DBAL\Exception $e) { + } catch (Exception) { return null; } } diff --git a/src/Services/Misc/FAIconGenerator.php b/src/Services/Misc/FAIconGenerator.php index b8ee4481..997e9f39 100644 --- a/src/Services/Misc/FAIconGenerator.php +++ b/src/Services/Misc/FAIconGenerator.php @@ -26,6 +26,9 @@ use App\Entity\Attachments\Attachment; use function in_array; use InvalidArgumentException; +/** + * @see \App\Tests\Services\Misc\FAIconGeneratorTest + */ class FAIconGenerator { protected const EXT_MAPPING = [ diff --git a/src/Services/Misc/RangeParser.php b/src/Services/Misc/RangeParser.php index ab6e9aba..f1a5db5b 100644 --- a/src/Services/Misc/RangeParser.php +++ b/src/Services/Misc/RangeParser.php @@ -45,6 +45,7 @@ use InvalidArgumentException; /** * This Parser allows to parse number ranges like 1-3, 4, 5. + * @see \App\Tests\Services\Misc\RangeParserTest */ class RangeParser { @@ -70,7 +71,7 @@ class RangeParser $ranges[] = $this->generateMinMaxRange($matches[1], $matches[2]); } elseif (is_numeric($number)) { $ranges[] = [(int) $number]; - } elseif (empty($number)) { //Allow empty tokens + } elseif ($number === '') { //Allow empty tokens continue; } else { throw new InvalidArgumentException('Invalid range encoutered: '.$number); @@ -94,7 +95,7 @@ class RangeParser $this->parse($range_str); return true; - } catch (InvalidArgumentException $exception) { + } catch (InvalidArgumentException) { return false; } } diff --git a/src/Services/Parameters/ParameterExtractor.php b/src/Services/Parameters/ParameterExtractor.php index a5c6a0c0..9eaf946a 100644 --- a/src/Services/Parameters/ParameterExtractor.php +++ b/src/Services/Parameters/ParameterExtractor.php @@ -47,6 +47,9 @@ use InvalidArgumentException; use function preg_match; +/** + * @see \App\Tests\Services\Parameters\ParameterExtractorTest + */ class ParameterExtractor { protected const ALLOWED_PARAM_SEPARATORS = [', ', "\n"]; @@ -74,7 +77,7 @@ class ParameterExtractor $split = $this->splitString($input); foreach ($split as $param_string) { $tmp = $this->stringToParam($param_string, $class); - if (null !== $tmp) { + if ($tmp instanceof AbstractParameter) { $parameters[] = $tmp; } } @@ -89,12 +92,12 @@ class ParameterExtractor $matches = []; preg_match($regex, $input, $matches); - if (!empty($matches)) { + if ($matches !== []) { [, $name, $value] = $matches; $value = trim($value); //Don't allow empty names or values (these are a sign of an invalid extracted string) - if (empty($name) || empty($value)) { + if ($name === '' || $value === '') { return null; } diff --git a/src/Services/Parts/PartLotWithdrawAddHelper.php b/src/Services/Parts/PartLotWithdrawAddHelper.php index a6f7060c..8e2d2d28 100644 --- a/src/Services/Parts/PartLotWithdrawAddHelper.php +++ b/src/Services/Parts/PartLotWithdrawAddHelper.php @@ -1,28 +1,26 @@ eventLogger = $eventLogger; - $this->eventCommentHelper = $eventCommentHelper; } /** * Checks whether the given part can - * @param PartLot $partLot - * @return bool */ public function canAdd(PartLot $partLot): bool { @@ -32,16 +30,11 @@ final class PartLotWithdrawAddHelper } //So far all other restrictions are defined at the storelocation level - if($partLot->getStorageLocation() === null) { + if(!$partLot->getStorageLocation() instanceof Storelocation) { return true; } - //We can not add parts if the storage location of the lot is marked as full - if($partLot->getStorageLocation()->isFull()) { - return false; - } - - return true; + return !$partLot->getStorageLocation()->isFull(); } public function canWithdraw(PartLot $partLot): bool @@ -50,13 +43,8 @@ final class PartLotWithdrawAddHelper if ($partLot->isInstockUnknown()) { return false; } - //Part must contain more than 0 parts - if ($partLot->getAmount() <= 0) { - return false; - } - - return true; + return $partLot->getAmount() > 0; } /** @@ -99,7 +87,7 @@ final class PartLotWithdrawAddHelper $this->eventLogger->log($event); //Apply the comment also to global events, so it gets associated with the elementChanged log entry - if (!$this->eventCommentHelper->isMessageSet() && !empty($comment)) { + if (!$this->eventCommentHelper->isMessageSet() && ($comment !== null && $comment !== '')) { $this->eventCommentHelper->setMessage($comment); } @@ -139,7 +127,7 @@ final class PartLotWithdrawAddHelper $this->eventLogger->log($event); //Apply the comment also to global events, so it gets associated with the elementChanged log entry - if (!$this->eventCommentHelper->isMessageSet() && !empty($comment)) { + if (!$this->eventCommentHelper->isMessageSet() && ($comment !== null && $comment !== '')) { $this->eventCommentHelper->setMessage($comment); } @@ -153,7 +141,6 @@ final class PartLotWithdrawAddHelper * @param PartLot $target The part lot to which the parts should be added * @param float $amount The amount of parts that should be moved * @param string|null $comment A comment describing the reason for the move - * @return void */ public function move(PartLot $origin, PartLot $target, float $amount, ?string $comment = null): void { @@ -194,8 +181,8 @@ final class PartLotWithdrawAddHelper $this->eventLogger->log($event); //Apply the comment also to global events, so it gets associated with the elementChanged log entry - if (!$this->eventCommentHelper->isMessageSet() && !empty($comment)) { + if (!$this->eventCommentHelper->isMessageSet() && ($comment !== null && $comment !== '')) { $this->eventCommentHelper->setMessage($comment); } } -} \ No newline at end of file +} diff --git a/src/Services/Parts/PartsTableActionHandler.php b/src/Services/Parts/PartsTableActionHandler.php index 3061d2f3..08c0715d 100644 --- a/src/Services/Parts/PartsTableActionHandler.php +++ b/src/Services/Parts/PartsTableActionHandler.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\Parts; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; @@ -33,19 +36,11 @@ use InvalidArgumentException; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Exception\AccessDeniedException; -use Symfony\Component\Security\Core\Security; final class PartsTableActionHandler { - private EntityManagerInterface $entityManager; - private Security $security; - private UrlGeneratorInterface $urlGenerator; - - public function __construct(EntityManagerInterface $entityManager, Security $security, UrlGeneratorInterface $urlGenerator) + public function __construct(private readonly EntityManagerInterface $entityManager, private readonly Security $security, private readonly UrlGeneratorInterface $urlGenerator) { - $this->entityManager = $entityManager; - $this->security = $security; - $this->urlGenerator = $urlGenerator; } /** @@ -86,10 +81,8 @@ final class PartsTableActionHandler if ($action === 'generate_label') { $targets = implode(',', array_map(static fn (Part $part) => $part->getID(), $selected_parts)); } else { //For lots we have to extract the part lots - $targets = implode(',', array_map(static function (Part $part) { - //We concat the lot IDs of every part with a comma (which are later concated with a comma too per part) - return implode(',', array_map(static fn (PartLot $lot) => $lot->getID(), $part->getPartLots()->toArray())); - }, $selected_parts)); + $targets = implode(',', array_map(static fn(Part $part): string => //We concat the lot IDs of every part with a comma (which are later concated with a comma too per part) +implode(',', array_map(static fn (PartLot $lot) => $lot->getID(), $part->getPartLots()->toArray())), $selected_parts)); } return new RedirectResponse( @@ -106,18 +99,11 @@ final class PartsTableActionHandler $matches = []; if (preg_match('/^export_(json|yaml|xml|csv)$/', $action, $matches)) { $ids = implode(',', array_map(static fn (Part $part) => $part->getID(), $selected_parts)); - switch ($target_id) { - case 1: - default: - $level = 'simple'; - break; - case 2: - $level = 'extended'; - break; - case 3: - $level = 'full'; - break; - } + $level = match ($target_id) { + 2 => 'extended', + 3 => 'full', + default => 'simple', + }; return new RedirectResponse( @@ -192,7 +178,7 @@ final class PartsTableActionHandler * * @throws AccessDeniedException */ - private function denyAccessUnlessGranted($attributes, $subject = null, string $message = 'Access Denied.'): void + private function denyAccessUnlessGranted(mixed $attributes, mixed $subject = null, string $message = 'Access Denied.'): void { if (!$this->security->isGranted($attributes, $subject)) { $exception = new AccessDeniedException($message); diff --git a/src/Services/Parts/PricedetailHelper.php b/src/Services/Parts/PricedetailHelper.php index a270c622..092cc278 100644 --- a/src/Services/Parts/PricedetailHelper.php +++ b/src/Services/Parts/PricedetailHelper.php @@ -32,14 +32,15 @@ use Locale; use function count; +/** + * @see \App\Tests\Services\Parts\PricedetailHelperTest + */ class PricedetailHelper { - protected string $base_currency; protected string $locale; - public function __construct(string $base_currency) + public function __construct(protected string $base_currency) { - $this->base_currency = $base_currency; $this->locale = Locale::getDefault(); } @@ -56,7 +57,7 @@ class PricedetailHelper foreach ($orderdetails as $orderdetail) { $pricedetails = $orderdetail->getPricedetails(); //The orderdetail must have pricedetails, otherwise this will not work! - if (0 === count($pricedetails)) { + if (0 === (is_countable($pricedetails) ? count($pricedetails) : 0)) { continue; } @@ -67,9 +68,7 @@ class PricedetailHelper } else { // We have to sort the pricedetails manually $array = $pricedetails->map( - static function (Pricedetail $pricedetail) { - return $pricedetail->getMinDiscountQuantity(); - } + static fn(Pricedetail $pricedetail) => $pricedetail->getMinDiscountQuantity() )->toArray(); sort($array); $max_amount = end($array); @@ -103,7 +102,7 @@ class PricedetailHelper foreach ($orderdetails as $orderdetail) { $pricedetails = $orderdetail->getPricedetails(); //The orderdetail must have pricedetails, otherwise this will not work! - if (0 === count($pricedetails)) { + if (0 === (is_countable($pricedetails) ? count($pricedetails) : 0)) { continue; } @@ -154,13 +153,13 @@ class PricedetailHelper $pricedetail = $orderdetail->findPriceForQty($amount); //When we don't have information about this amount, ignore it - if (null === $pricedetail) { + if (!$pricedetail instanceof Pricedetail) { continue; } $converted = $this->convertMoneyToCurrency($pricedetail->getPricePerUnit(), $pricedetail->getCurrency(), $currency); //Ignore price information that can not be converted to base currency. - if (null !== $converted) { + if ($converted instanceof BigDecimal) { $avg = $avg->plus($converted); ++$count; } @@ -193,9 +192,9 @@ class PricedetailHelper $val_base = $value; //Convert value to base currency - if (null !== $originCurrency) { + if ($originCurrency instanceof Currency) { //Without an exchange rate we can not calculate the exchange rate - if (null === $originCurrency->getExchangeRate() || $originCurrency->getExchangeRate()->isZero()) { + if (!$originCurrency->getExchangeRate() instanceof BigDecimal || $originCurrency->getExchangeRate()->isZero()) { return null; } @@ -204,9 +203,9 @@ class PricedetailHelper $val_target = $val_base; //Convert value in base currency to target currency - if (null !== $targetCurrency) { + if ($targetCurrency instanceof Currency) { //Without an exchange rate we can not calculate the exchange rate - if (null === $targetCurrency->getExchangeRate()) { + if (!$targetCurrency->getExchangeRate() instanceof BigDecimal) { return null; } diff --git a/src/Services/ProjectSystem/ProjectBuildHelper.php b/src/Services/ProjectSystem/ProjectBuildHelper.php index 0e091547..269c7e4c 100644 --- a/src/Services/ProjectSystem/ProjectBuildHelper.php +++ b/src/Services/ProjectSystem/ProjectBuildHelper.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\ProjectSystem; +use App\Entity\Parts\Part; use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; use App\Helpers\Projects\ProjectBuildRequest; use App\Services\Parts\PartLotWithdrawAddHelper; +/** + * @see \App\Tests\Services\ProjectSystem\ProjectBuildHelperTest + */ class ProjectBuildHelper { - private PartLotWithdrawAddHelper $withdraw_add_helper; - - public function __construct(PartLotWithdrawAddHelper $withdraw_add_helper) + public function __construct(private readonly PartLotWithdrawAddHelper $withdraw_add_helper) { - $this->withdraw_add_helper = $withdraw_add_helper; } /** * Returns the maximum buildable amount of the given BOM entry based on the stock of the used parts. * This function only works for BOM entries that are associated with a part. - * @param ProjectBOMEntry $projectBOMEntry - * @return int */ public function getMaximumBuildableCountForBOMEntry(ProjectBOMEntry $projectBOMEntry): int { $part = $projectBOMEntry->getPart(); - if ($part === null) { + if (!$part instanceof Part) { throw new \InvalidArgumentException('This function cannot determine the maximum buildable count for a BOM entry without a part!'); } @@ -59,13 +60,11 @@ class ProjectBuildHelper /** * Returns the maximum buildable amount of the given project, based on the stock of the used parts in the BOM. - * @param Project $project - * @return int */ public function getMaximumBuildableCount(Project $project): int { $maximum_buildable_count = PHP_INT_MAX; - foreach ($project->getBOMEntries() as $bom_entry) { + foreach ($project->getBomEntries() as $bom_entry) { //Skip BOM entries without a part (as we can not determine that) if (!$bom_entry->isPartBomEntry()) { continue; @@ -81,9 +80,7 @@ class ProjectBuildHelper /** * Checks if the given project can be built with the current stock. * This means that the maximum buildable count is greater or equal than the requested $number_of_projects - * @param Project $project - * @parm int $number_of_builds - * @return bool + * @param int $number_of_builds */ public function isProjectBuildable(Project $project, int $number_of_builds = 1): bool { @@ -93,9 +90,6 @@ class ProjectBuildHelper /** * Check if the given BOM entry can be built with the current stock. * This means that the maximum buildable count is greater or equal than the requested $number_of_projects - * @param ProjectBOMEntry $bom_entry - * @param int $number_of_builds - * @return bool */ public function isBOMEntryBuildable(ProjectBOMEntry $bom_entry, int $number_of_builds = 1): bool { @@ -120,7 +114,7 @@ class ProjectBuildHelper $part = $bomEntry->getPart(); //Skip BOM entries without a part (as we can not determine that) - if ($part === null) { + if (!$part instanceof Part) { continue; } @@ -138,8 +132,6 @@ class ProjectBuildHelper * Withdraw the parts from the stock using the given ProjectBuildRequest and create the build parts entries, if needed. * The ProjectBuildRequest has to be validated before!! * You have to flush changes to DB afterward - * @param ProjectBuildRequest $buildRequest - * @return void */ public function doBuild(ProjectBuildRequest $buildRequest): void { @@ -159,4 +151,4 @@ class ProjectBuildHelper $this->withdraw_add_helper->add($buildRequest->getBuildsPartLot(), $buildRequest->getNumberOfBuilds(), $message); } } -} \ No newline at end of file +} diff --git a/src/Services/ProjectSystem/ProjectBuildPartHelper.php b/src/Services/ProjectSystem/ProjectBuildPartHelper.php index 136e2ff7..218f456e 100644 --- a/src/Services/ProjectSystem/ProjectBuildPartHelper.php +++ b/src/Services/ProjectSystem/ProjectBuildPartHelper.php @@ -1,17 +1,20 @@ . */ - namespace App\Services\Tools; use App\Entity\PriceInformations\Currency; @@ -27,13 +29,8 @@ use Swap\Swap; class ExchangeRateUpdater { - private string $base_currency; - private Swap $swap; - - public function __construct(string $base_currency, Swap $swap) + public function __construct(private readonly string $base_currency, private readonly Swap $swap) { - $this->base_currency = $base_currency; - $this->swap = $swap; } /** diff --git a/src/Services/Tools/StatisticsHelper.php b/src/Services/Tools/StatisticsHelper.php index f31cb440..0ee736f9 100644 --- a/src/Services/Tools/StatisticsHelper.php +++ b/src/Services/Tools/StatisticsHelper.php @@ -62,13 +62,11 @@ use InvalidArgumentException; class StatisticsHelper { - protected EntityManagerInterface $em; protected PartRepository $part_repo; protected AttachmentRepository $attachment_repo; - public function __construct(EntityManagerInterface $em) + public function __construct(protected EntityManagerInterface $em) { - $this->em = $em; $this->part_repo = $this->em->getRepository(Part::class); $this->attachment_repo = $this->em->getRepository(Attachment::class); } diff --git a/src/Services/Tools/TagFinder.php b/src/Services/Tools/TagFinder.php index d52b3008..bfc7c9db 100644 --- a/src/Services/Tools/TagFinder.php +++ b/src/Services/Tools/TagFinder.php @@ -34,11 +34,8 @@ use function array_slice; */ class TagFinder { - protected EntityManagerInterface $em; - - public function __construct(EntityManagerInterface $entityManager) + public function __construct(protected EntityManagerInterface $em) { - $this->em = $entityManager; } /** @@ -78,7 +75,7 @@ class TagFinder //Iterate over each possible tags (which are comma separated) and extract tags which match our keyword foreach ($possible_tags as $tags) { - $tags = explode(',', $tags['tags']); + $tags = explode(',', (string) $tags['tags']); $results = array_merge($results, preg_grep($keyword_regex, $tags)); } diff --git a/src/Services/TranslationExtractor/PermissionExtractor.php b/src/Services/TranslationExtractor/PermissionExtractor.php index 03acd5db..e17cba7a 100644 --- a/src/Services/TranslationExtractor/PermissionExtractor.php +++ b/src/Services/TranslationExtractor/PermissionExtractor.php @@ -32,7 +32,7 @@ use Symfony\Component\Translation\MessageCatalogue; */ final class PermissionExtractor implements ExtractorInterface { - private array $permission_structure; + private readonly array $permission_structure; private bool $finished = false; public function __construct(PermissionManager $resolver) diff --git a/src/Services/Trees/NodesListBuilder.php b/src/Services/Trees/NodesListBuilder.php index 66ec2d40..c3e06455 100644 --- a/src/Services/Trees/NodesListBuilder.php +++ b/src/Services/Trees/NodesListBuilder.php @@ -31,18 +31,12 @@ use Symfony\Contracts\Cache\TagAwareCacheInterface; /** * This service gives you a flat list containing all structured entities in the order of the structure. + * @see \App\Tests\Services\Trees\NodesListBuilderTest */ class NodesListBuilder { - protected EntityManagerInterface $em; - protected TagAwareCacheInterface $cache; - protected UserCacheKeyGenerator $keyGenerator; - - public function __construct(EntityManagerInterface $em, TagAwareCacheInterface $treeCache, UserCacheKeyGenerator $keyGenerator) + public function __construct(protected EntityManagerInterface $em, protected TagAwareCacheInterface $cache, protected UserCacheKeyGenerator $keyGenerator) { - $this->em = $em; - $this->keyGenerator = $keyGenerator; - $this->cache = $treeCache; } /** @@ -56,7 +50,7 @@ class NodesListBuilder */ public function typeToNodesList(string $class_name, ?AbstractStructuralDBElement $parent = null): array { - $parent_id = null !== $parent ? $parent->getID() : '0'; + $parent_id = $parent instanceof AbstractStructuralDBElement ? $parent->getID() : '0'; // Backslashes are not allowed in cache keys $secure_class_name = str_replace('\\', '_', $class_name); $key = 'list_'.$this->keyGenerator->generateKey().'_'.$secure_class_name.$parent_id; @@ -72,15 +66,19 @@ class NodesListBuilder } /** - * Returns a flattened list of all (recursive) children elements of the given AbstractStructuralDBElement. - * The value is cached for performance reasons. + * Returns a flattened list of all (recursive) children elements of the given AbstractStructuralDBElement. + * The value is cached for performance reasons. * * @template T of AbstractStructuralDBElement - * @param T $element - * @return T[] + * + * @param T $element + * + * @return AbstractStructuralDBElement[] + * + * @phpstan-return list */ public function getChildrenFlatList(AbstractStructuralDBElement $element): array { - return $this->typeToNodesList(get_class($element), $element); + return $this->typeToNodesList($element::class, $element); } } diff --git a/src/Services/Trees/SidebarTreeUpdater.php b/src/Services/Trees/SidebarTreeUpdater.php index 13c3fb6c..0d7ccee9 100644 --- a/src/Services/Trees/SidebarTreeUpdater.php +++ b/src/Services/Trees/SidebarTreeUpdater.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\Trees; use Symfony\Contracts\Cache\CacheInterface; @@ -27,19 +29,18 @@ use Symfony\Contracts\Cache\TagAwareCacheInterface; final class SidebarTreeUpdater { private const CACHE_KEY = 'sidebar_tree_updated'; - private const TTL = 60 * 60 * 24; // 24 hours + private const TTL = 60 * 60 * 24; - private CacheInterface $cache; - - public function __construct(TagAwareCacheInterface $treeCache) + public function __construct( + // 24 hours + private readonly TagAwareCacheInterface $cache + ) { - $this->cache = $treeCache; } /** * Returns the time when the sidebar tree was updated the last time. * The frontend uses this information to reload the sidebar tree. - * @return \DateTimeInterface */ public function getLastTreeUpdate(): \DateTimeInterface { @@ -52,4 +53,4 @@ final class SidebarTreeUpdater return new \DateTime(); }); } -} \ No newline at end of file +} diff --git a/src/Services/Trees/StructuralElementRecursionHelper.php b/src/Services/Trees/StructuralElementRecursionHelper.php index 3096740c..bc46d7f7 100644 --- a/src/Services/Trees/StructuralElementRecursionHelper.php +++ b/src/Services/Trees/StructuralElementRecursionHelper.php @@ -27,11 +27,8 @@ use Doctrine\ORM\EntityManagerInterface; class StructuralElementRecursionHelper { - protected EntityManagerInterface $em; - - public function __construct(EntityManagerInterface $em) + public function __construct(protected EntityManagerInterface $em) { - $this->em = $em; } /** diff --git a/src/Services/Trees/ToolsTreeBuilder.php b/src/Services/Trees/ToolsTreeBuilder.php index b146d694..d1c01063 100644 --- a/src/Services/Trees/ToolsTreeBuilder.php +++ b/src/Services/Trees/ToolsTreeBuilder.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Services\Trees; +use Symfony\Bundle\SecurityBundle\Security; use App\Entity\Attachments\AttachmentType; use App\Entity\ProjectSystem\Project; use App\Entity\LabelSystem\LabelProfile; @@ -38,7 +39,6 @@ use App\Entity\UserSystem\User; use App\Helpers\Trees\TreeViewNode; use App\Services\UserSystem\UserCacheKeyGenerator; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Security\Core\Security; use Symfony\Contracts\Cache\ItemInterface; use Symfony\Contracts\Cache\TagAwareCacheInterface; use Symfony\Contracts\Translation\TranslatorInterface; @@ -49,24 +49,8 @@ use Symfony\Contracts\Translation\TranslatorInterface; */ class ToolsTreeBuilder { - protected TranslatorInterface $translator; - protected UrlGeneratorInterface $urlGenerator; - protected UserCacheKeyGenerator $keyGenerator; - protected TagAwareCacheInterface $cache; - protected Security $security; - - public function __construct(TranslatorInterface $translator, UrlGeneratorInterface $urlGenerator, - TagAwareCacheInterface $treeCache, UserCacheKeyGenerator $keyGenerator, - Security $security) + public function __construct(protected TranslatorInterface $translator, protected UrlGeneratorInterface $urlGenerator, protected TagAwareCacheInterface $cache, protected UserCacheKeyGenerator $keyGenerator, protected Security $security) { - $this->translator = $translator; - $this->urlGenerator = $urlGenerator; - - $this->cache = $treeCache; - - $this->keyGenerator = $keyGenerator; - - $this->security = $security; } /** @@ -84,20 +68,20 @@ class ToolsTreeBuilder $item->tag(['tree_tools', 'groups', $this->keyGenerator->generateKey()]); $tree = []; - if (!empty($this->getToolsNode())) { + if ($this->getToolsNode() !== []) { $tree[] = (new TreeViewNode($this->translator->trans('tree.tools.tools'), null, $this->getToolsNode())) ->setIcon('fa-fw fa-treeview fa-solid fa-toolbox'); } - if (!empty($this->getEditNodes())) { + if ($this->getEditNodes() !== []) { $tree[] = (new TreeViewNode($this->translator->trans('tree.tools.edit'), null, $this->getEditNodes())) ->setIcon('fa-fw fa-treeview fa-solid fa-pen-to-square'); } - if (!empty($this->getShowNodes())) { + if ($this->getShowNodes() !== []) { $tree[] = (new TreeViewNode($this->translator->trans('tree.tools.show'), null, $this->getShowNodes())) ->setIcon('fa-fw fa-treeview fa-solid fa-eye'); } - if (!empty($this->getSystemNodes())) { + if ($this->getSystemNodes() !== []) { $tree[] = (new TreeViewNode($this->translator->trans('tree.tools.system'), null, $this->getSystemNodes())) ->setIcon('fa-fw fa-treeview fa-solid fa-server'); } diff --git a/src/Services/Trees/TreeViewGenerator.php b/src/Services/Trees/TreeViewGenerator.php index 29d09f9b..ca93873c 100644 --- a/src/Services/Trees/TreeViewGenerator.php +++ b/src/Services/Trees/TreeViewGenerator.php @@ -45,28 +45,13 @@ use Symfony\Contracts\Translation\TranslatorInterface; use function count; +/** + * @see \App\Tests\Services\Trees\TreeViewGeneratorTest + */ class TreeViewGenerator { - protected EntityURLGenerator $urlGenerator; - protected EntityManagerInterface $em; - protected TagAwareCacheInterface $cache; - protected UserCacheKeyGenerator $keyGenerator; - protected TranslatorInterface $translator; - - protected bool $rootNodeExpandedByDefault; - protected bool $rootNodeEnabled; - - public function __construct(EntityURLGenerator $URLGenerator, EntityManagerInterface $em, - TagAwareCacheInterface $treeCache, UserCacheKeyGenerator $keyGenerator, TranslatorInterface $translator, bool $rootNodeExpandedByDefault, bool $rootNodeEnabled) + public function __construct(protected EntityURLGenerator $urlGenerator, protected EntityManagerInterface $em, protected TagAwareCacheInterface $cache, protected UserCacheKeyGenerator $keyGenerator, protected TranslatorInterface $translator, protected bool $rootNodeExpandedByDefault, protected bool $rootNodeEnabled) { - $this->urlGenerator = $URLGenerator; - $this->em = $em; - $this->cache = $treeCache; - $this->keyGenerator = $keyGenerator; - $this->translator = $translator; - - $this->rootNodeExpandedByDefault = $rootNodeExpandedByDefault; - $this->rootNodeEnabled = $rootNodeEnabled; } /** @@ -92,7 +77,7 @@ class TreeViewGenerator $href = $this->urlGenerator->createURL(new $class()); $new_node = new TreeViewNode($this->translator->trans('entity.tree.new'), $href); //When the id of the selected element is null, then we have a new element, and we need to select "new" node - if (null === $selectedElement || null === $selectedElement->getID()) { + if (!$selectedElement instanceof AbstractDBElement || null === $selectedElement->getID()) { $new_node->setSelected(true); } $head[] = $new_node; @@ -116,21 +101,21 @@ class TreeViewGenerator $recursiveIterator = new RecursiveIteratorIterator($treeIterator, RecursiveIteratorIterator::SELF_FIRST); foreach ($recursiveIterator as $item) { /** @var TreeViewNode $item */ - if (null !== $selectedElement && $item->getId() === $selectedElement->getID()) { + if ($selectedElement instanceof AbstractDBElement && $item->getId() === $selectedElement->getID()) { $item->setSelected(true); } - if (!empty($item->getNodes())) { + if ($item->getNodes() !== null && $item->getNodes() !== []) { $item->addTag((string) count($item->getNodes())); } - if (!empty($href_type) && null !== $item->getId()) { + if ($href_type !== '' && null !== $item->getId()) { $entity = $this->em->getPartialReference($class, $item->getId()); $item->setHref($this->urlGenerator->getURL($entity, $href_type)); } //Translate text if text starts with $$ - if (0 === strpos($item->getText(), '$$')) { + if (str_starts_with($item->getText(), '$$')) { $item->setText($this->translator->trans(substr($item->getText(), 2))); } } @@ -148,43 +133,29 @@ class TreeViewGenerator protected function entityClassToRootNodeString(string $class): string { - switch ($class) { - case Category::class: - return $this->translator->trans('category.labelp'); - case Storelocation::class: - return $this->translator->trans('storelocation.labelp'); - case Footprint::class: - return $this->translator->trans('footprint.labelp'); - case Manufacturer::class: - return $this->translator->trans('manufacturer.labelp'); - case Supplier::class: - return $this->translator->trans('supplier.labelp'); - case Project::class: - return $this->translator->trans('project.labelp'); - default: - return $this->translator->trans('tree.root_node.text'); - } + return match ($class) { + Category::class => $this->translator->trans('category.labelp'), + Storelocation::class => $this->translator->trans('storelocation.labelp'), + Footprint::class => $this->translator->trans('footprint.labelp'), + Manufacturer::class => $this->translator->trans('manufacturer.labelp'), + Supplier::class => $this->translator->trans('supplier.labelp'), + Project::class => $this->translator->trans('project.labelp'), + default => $this->translator->trans('tree.root_node.text'), + }; } protected function entityClassToRootNodeIcon(string $class): ?string { $icon = "fa-fw fa-treeview fa-solid "; - switch ($class) { - case Category::class: - return $icon . 'fa-tags'; - case Storelocation::class: - return $icon . 'fa-cube'; - case Footprint::class: - return $icon . 'fa-microchip'; - case Manufacturer::class: - return $icon . 'fa-industry'; - case Supplier::class: - return $icon . 'fa-truck'; - case Project::class: - return $icon . 'fa-archive'; - default: - return null; - } + return match ($class) { + Category::class => $icon . 'fa-tags', + Storelocation::class => $icon . 'fa-cube', + Footprint::class => $icon . 'fa-microchip', + Manufacturer::class => $icon . 'fa-industry', + Supplier::class => $icon . 'fa-truck', + Project::class => $icon . 'fa-archive', + default => null, + }; } /** @@ -202,7 +173,7 @@ class TreeViewGenerator if (!is_a($class, AbstractNamedDBElement::class, true)) { throw new InvalidArgumentException('$class must be a class string that implements StructuralDBElement or NamedDBElement!'); } - if (null !== $parent && !is_a($parent, $class)) { + if ($parent instanceof AbstractStructuralDBElement && !$parent instanceof $class) { throw new InvalidArgumentException('$parent must be of the type $class!'); } @@ -210,7 +181,7 @@ class TreeViewGenerator $repo = $this->em->getRepository($class); //If we just want a part of a tree, don't cache it - if (null !== $parent) { + if ($parent instanceof AbstractStructuralDBElement) { return $repo->getGenericNodeTree($parent); } diff --git a/src/Services/UserSystem/PasswordResetManager.php b/src/Services/UserSystem/PasswordResetManager.php index 7b8a5be3..2349297b 100644 --- a/src/Services/UserSystem/PasswordResetManager.php +++ b/src/Services/UserSystem/PasswordResetManager.php @@ -35,21 +35,13 @@ use Symfony\Contracts\Translation\TranslatorInterface; class PasswordResetManager { - protected MailerInterface $mailer; - protected EntityManagerInterface $em; protected PasswordHasherInterface $passwordEncoder; - protected TranslatorInterface $translator; - protected UserPasswordHasherInterface $userPasswordEncoder; - public function __construct(MailerInterface $mailer, EntityManagerInterface $em, - TranslatorInterface $translator, UserPasswordHasherInterface $userPasswordEncoder, + public function __construct(protected MailerInterface $mailer, protected EntityManagerInterface $em, + protected TranslatorInterface $translator, protected UserPasswordHasherInterface $userPasswordEncoder, PasswordHasherFactoryInterface $encoderFactory) { - $this->em = $em; - $this->mailer = $mailer; $this->passwordEncoder = $encoderFactory->getPasswordHasher(User::class); - $this->translator = $translator; - $this->userPasswordEncoder = $userPasswordEncoder; } public function request(string $name_or_email): void @@ -59,7 +51,7 @@ class PasswordResetManager //Try to find a user by the given string $user = $repo->findByEmailOrName($name_or_email); //Do nothing if no user was found - if (null === $user) { + if (!$user instanceof User) { return; } @@ -71,7 +63,7 @@ class PasswordResetManager $expiration_date->add(date_interval_create_from_date_string('1 day')); $user->setPwResetExpires($expiration_date); - if (!empty($user->getEmail())) { + if ($user->getEmail() !== null && $user->getEmail() !== '') { $address = new Address($user->getEmail(), $user->getFullName()); $mail = new TemplatedEmail(); $mail->to($address); @@ -109,7 +101,7 @@ class PasswordResetManager $user = $repo->findOneBy(['name' => $username]); //If no user matching the name, show an error message - if (null === $user) { + if (!$user instanceof User) { return false; } diff --git a/src/Services/UserSystem/PermissionManager.php b/src/Services/UserSystem/PermissionManager.php index 1e3209fa..fa591a91 100644 --- a/src/Services/UserSystem/PermissionManager.php +++ b/src/Services/UserSystem/PermissionManager.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace App\Services\UserSystem; +use App\Entity\Base\AbstractStructuralDBElement; use App\Configuration\PermissionsConfiguration; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; @@ -36,23 +37,21 @@ use Symfony\Component\Yaml\Yaml; * This class manages the permissions of users and groups. * Permissions are defined in the config/permissions.yaml file, and are parsed and resolved by this class using the * user and hierachical group PermissionData information. + * @see \App\Tests\Services\UserSystem\PermissionManagerTest */ class PermissionManager { protected $permission_structure; - - protected bool $is_debug; protected string $cache_file; /** * PermissionResolver constructor. */ - public function __construct(bool $kernel_debug, string $kernel_cache_dir) + public function __construct(protected readonly bool $kernel_debug_enabled, string $kernel_cache_dir) { $cache_dir = $kernel_cache_dir; //Here the cached structure will be saved. $this->cache_file = $cache_dir.'/permissions.php.cache'; - $this->is_debug = $kernel_debug; $this->permission_structure = $this->generatePermissionStructure(); } @@ -113,7 +112,7 @@ class PermissionManager /** @var Group $parent */ $parent = $user->getGroup(); - while (null !== $parent) { //The top group, has parent == null + while ($parent instanceof Group) { //The top group, has parent == null //Check if our current element gives an info about disallow/allow $allowed = $this->dontInherit($parent, $permission, $operation); if (null !== $allowed) { @@ -196,8 +195,6 @@ class PermissionManager /** * This functions sets all operations mentioned in the alsoSet value of a permission, so that the structure is always valid. - * @param HasPermissionsInterface $user - * @return void */ public function ensureCorrectSetOperations(HasPermissionsInterface $user): void { @@ -215,12 +212,7 @@ class PermissionManager //Set every op listed in also Set foreach ($op['alsoSet'] as $set_also) { //If the alsoSet value contains a dot then we set the operation of another permission - if (false !== strpos($set_also, '.')) { - [$set_perm, $set_op] = explode('.', $set_also); - } else { - //Else we set the operation of the same permission - [$set_perm, $set_op] = [$perm_key, $set_also]; - } + [$set_perm, $set_op] = str_contains((string) $set_also, '.') ? explode('.', (string) $set_also) : [$perm_key, $set_also]; //Check if we change the value of the permission if ($this->dontInherit($user, $set_perm, $set_op) !== true) { @@ -237,9 +229,6 @@ class PermissionManager /** * Sets all possible operations of all possible permissions of the given entity to the given value. - * @param HasPermissionsInterface $perm_holder - * @param bool|null $new_value - * @return void */ public function setAllPermissions(HasPermissionsInterface $perm_holder, ?bool $new_value): void { @@ -253,11 +242,6 @@ class PermissionManager /** * Sets all operations of the given permissions to the given value. * Please note that you have to call ensureCorrectSetOperations() after this function, to ensure that all alsoSet values are set. - * - * @param HasPermissionsInterface $perm_holder - * @param string $permission - * @param bool|null $new_value - * @return void */ public function setAllOperationsOfPermission(HasPermissionsInterface $perm_holder, string $permission, ?bool $new_value): void { @@ -272,11 +256,6 @@ class PermissionManager /** * This function sets all operations of the given permission to the given value, except the ones listed in the except array. - * @param HasPermissionsInterface $perm_holder - * @param string $permission - * @param bool|null $new_value - * @param array $except - * @return void */ public function setAllOperationsOfPermissionExcept(HasPermissionsInterface $perm_holder, string $permission, ?bool $new_value, array $except): void { @@ -294,7 +273,7 @@ class PermissionManager protected function generatePermissionStructure() { - $cache = new ConfigCache($this->cache_file, $this->is_debug); + $cache = new ConfigCache($this->cache_file, $this->kernel_debug_enabled); //Check if the cache is fresh, else regenerate it. if (!$cache->isFresh()) { diff --git a/src/Services/UserSystem/PermissionPresetsHelper.php b/src/Services/UserSystem/PermissionPresetsHelper.php index 7f7dc405..15a29b13 100644 --- a/src/Services/UserSystem/PermissionPresetsHelper.php +++ b/src/Services/UserSystem/PermissionPresetsHelper.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\UserSystem; use App\Entity\UserSystem\PermissionData; @@ -25,18 +27,15 @@ use App\Security\Interfaces\HasPermissionsInterface; class PermissionPresetsHelper { - public const PRESET_ALL_INHERIT = 'all_inherit'; - public const PRESET_ALL_FORBID = 'all_forbid'; - public const PRESET_ALL_ALLOW = 'all_allow'; - public const PRESET_READ_ONLY = 'read_only'; - public const PRESET_EDITOR = 'editor'; - public const PRESET_ADMIN = 'admin'; + final public const PRESET_ALL_INHERIT = 'all_inherit'; + final public const PRESET_ALL_FORBID = 'all_forbid'; + final public const PRESET_ALL_ALLOW = 'all_allow'; + final public const PRESET_READ_ONLY = 'read_only'; + final public const PRESET_EDITOR = 'editor'; + final public const PRESET_ADMIN = 'admin'; - private PermissionManager $permissionResolver; - - public function __construct(PermissionManager $permissionResolver) + public function __construct(private readonly PermissionManager $permissionResolver) { - $this->permissionResolver = $permissionResolver; } /** @@ -44,7 +43,6 @@ class PermissionPresetsHelper * The permission data will be reset during the process and then the preset will be applied. * * @param string $preset_name The name of the preset to use - * @return HasPermissionsInterface */ public function applyPreset(HasPermissionsInterface $perm_holder, string $preset_name): HasPermissionsInterface { @@ -174,15 +172,15 @@ class PermissionPresetsHelper return $perm_holder; } - private function AllForbid(HasPermissionsInterface $perm_holder): HasPermissionsInterface + private function allForbid(HasPermissionsInterface $perm_holder): HasPermissionsInterface { $this->permissionResolver->setAllPermissions($perm_holder, PermissionData::DISALLOW); return $perm_holder; } - private function AllAllow(HasPermissionsInterface $perm_holder): HasPermissionsInterface + private function allAllow(HasPermissionsInterface $perm_holder): HasPermissionsInterface { $this->permissionResolver->setAllPermissions($perm_holder, PermissionData::ALLOW); return $perm_holder; } -} \ No newline at end of file +} diff --git a/src/Services/UserSystem/PermissionSchemaUpdater.php b/src/Services/UserSystem/PermissionSchemaUpdater.php index f182c018..5fb08182 100644 --- a/src/Services/UserSystem/PermissionSchemaUpdater.php +++ b/src/Services/UserSystem/PermissionSchemaUpdater.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\UserSystem; use App\Entity\UserSystem\Group; @@ -25,11 +27,13 @@ use App\Entity\UserSystem\PermissionData; use App\Entity\UserSystem\User; use App\Security\Interfaces\HasPermissionsInterface; +/** + * @see \App\Tests\Services\UserSystem\PermissionSchemaUpdaterTest + */ class PermissionSchemaUpdater { /** * Check if the given user/group needs an update of its permission schema. - * @param HasPermissionsInterface $holder * @return bool True if the permission schema needs an update, false otherwise. */ public function isSchemaUpdateNeeded(HasPermissionsInterface $holder): bool @@ -42,12 +46,11 @@ class PermissionSchemaUpdater /** * Upgrades the permission schema of the given user/group to the chosen version. * Please note that this function does not flush the changes to DB! - * @param HasPermissionsInterface $holder - * @param int $target_version * @return bool True, if an upgrade was done, false if it was not needed. */ public function upgradeSchema(HasPermissionsInterface $holder, int $target_version = PermissionData::CURRENT_SCHEMA_VERSION): bool { + $e = null; if ($target_version > PermissionData::CURRENT_SCHEMA_VERSION) { throw new \InvalidArgumentException('The target version is higher than the maximum possible schema version!'); } @@ -62,11 +65,9 @@ class PermissionSchemaUpdater $reflectionClass = new \ReflectionClass(self::class); try { $method = $reflectionClass->getMethod('upgradeSchemaToVersion'.($n + 1)); - //Set the method accessible, so we can call it (needed for PHP < 8.1) - $method->setAccessible(true); $method->invoke($this, $holder); } catch (\ReflectionException $e) { - throw new \RuntimeException('Could not find update method for schema version '.($n + 1)); + throw new \RuntimeException('Could not find update method for schema version '.($n + 1), $e->getCode(), $e); } //Bump the schema version @@ -80,8 +81,6 @@ class PermissionSchemaUpdater /** * Upgrades the permission schema of the given group and all of its parent groups to the chosen version. * Please note that this function does not flush the changes to DB! - * @param Group $group - * @param int $target_version * @return bool True if an upgrade was done, false if it was not needed. */ public function groupUpgradeSchemaRecursively(Group $group, int $target_version = PermissionData::CURRENT_SCHEMA_VERSION): bool @@ -101,21 +100,19 @@ class PermissionSchemaUpdater /** * Upgrades the permissions schema of the given users and its parent (including parent groups) to the chosen version. * Please note that this function does not flush the changes to DB! - * @param User $user - * @param int $target_version * @return bool True if an upgrade was done, false if it was not needed. */ public function userUpgradeSchemaRecursively(User $user, int $target_version = PermissionData::CURRENT_SCHEMA_VERSION): bool { $updated = $this->upgradeSchema($user, $target_version); - if ($user->getGroup()) { + if ($user->getGroup() instanceof Group) { $updated = $this->groupUpgradeSchemaRecursively($user->getGroup(), $target_version) || $updated; } return $updated; } - private function upgradeSchemaToVersion1(HasPermissionsInterface $holder): void + private function upgradeSchemaToVersion1(HasPermissionsInterface $holder): void //@phpstan-ignore-line This is called via reflection { //Use the part edit permission to set the preset value for the new part stock permission if ( @@ -132,7 +129,7 @@ class PermissionSchemaUpdater } } - private function upgradeSchemaToVersion2(HasPermissionsInterface $holder): void + private function upgradeSchemaToVersion2(HasPermissionsInterface $holder): void //@phpstan-ignore-line This is called via reflection { //If the projects permissions are not defined yet, rename devices permission to projects (just copy its data over) if (!$holder->getPermissions()->isAnyOperationOfPermissionSet('projects')) { @@ -141,4 +138,4 @@ class PermissionSchemaUpdater $holder->getPermissions()->removePermission('devices'); } } -} \ No newline at end of file +} diff --git a/src/Services/UserSystem/TFA/BackupCodeGenerator.php b/src/Services/UserSystem/TFA/BackupCodeGenerator.php index 942dd050..a13b9804 100644 --- a/src/Services/UserSystem/TFA/BackupCodeGenerator.php +++ b/src/Services/UserSystem/TFA/BackupCodeGenerator.php @@ -27,11 +27,11 @@ use RuntimeException; /** * This class generates random backup codes for two-factor authentication. + * @see \App\Tests\Services\UserSystem\TFA\BackupCodeGeneratorTest */ class BackupCodeGenerator { protected int $code_length; - protected int $code_count; /** * BackupCodeGenerator constructor. @@ -39,7 +39,7 @@ class BackupCodeGenerator * @param int $code_length how many characters a single code should have * @param int $code_count how many codes are generated for a whole backup set */ - public function __construct(int $code_length, int $code_count) + public function __construct(int $code_length, protected int $code_count) { if ($code_length > 32) { throw new RuntimeException('Backup code can have maximum 32 digits!'); @@ -47,8 +47,6 @@ class BackupCodeGenerator if ($code_length < 6) { throw new RuntimeException('Code must have at least 6 digits to ensure security!'); } - - $this->code_count = $code_count; $this->code_length = $code_length; } diff --git a/src/Services/UserSystem/TFA/BackupCodeManager.php b/src/Services/UserSystem/TFA/BackupCodeManager.php index fb98a33e..07484618 100644 --- a/src/Services/UserSystem/TFA/BackupCodeManager.php +++ b/src/Services/UserSystem/TFA/BackupCodeManager.php @@ -26,14 +26,12 @@ use App\Entity\UserSystem\User; /** * This services offers methods to manage backup codes for two-factor authentication. + * @see \App\Tests\Services\UserSystem\TFA\BackupCodeManagerTest */ class BackupCodeManager { - protected BackupCodeGenerator $backupCodeGenerator; - - public function __construct(BackupCodeGenerator $backupCodeGenerator) + public function __construct(protected BackupCodeGenerator $backupCodeGenerator) { - $this->backupCodeGenerator = $backupCodeGenerator; } /** @@ -42,7 +40,7 @@ class BackupCodeManager */ public function enableBackupCodes(User $user): void { - if (empty($user->getBackupCodes())) { + if ($user->getBackupCodes() === []) { $this->regenerateBackupCodes($user); } } diff --git a/src/Services/UserSystem/UserAvatarHelper.php b/src/Services/UserSystem/UserAvatarHelper.php index 95b94dca..dd06ce1b 100644 --- a/src/Services/UserSystem/UserAvatarHelper.php +++ b/src/Services/UserSystem/UserAvatarHelper.php @@ -1,4 +1,7 @@ . */ - namespace App\Services\UserSystem; +use Imagine\Exception\RuntimeException; use App\Entity\Attachments\Attachment; use App\Entity\Attachments\AttachmentType; use App\Entity\Attachments\UserAttachment; @@ -33,35 +36,22 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; class UserAvatarHelper { - private bool $use_gravatar; - private Packages $packages; - private AttachmentURLGenerator $attachmentURLGenerator; - private FilterService $filterService; - private EntityManagerInterface $entityManager; - private AttachmentSubmitHandler $submitHandler; - - public function __construct(bool $use_gravatar, Packages $packages, AttachmentURLGenerator $attachmentURLGenerator, - FilterService $filterService, EntityManagerInterface $entityManager, AttachmentSubmitHandler $attachmentSubmitHandler) + public function __construct(private readonly bool $use_gravatar, private readonly Packages $packages, private readonly AttachmentURLGenerator $attachmentURLGenerator, private readonly FilterService $filterService, private readonly EntityManagerInterface $entityManager, private readonly AttachmentSubmitHandler $submitHandler) { - $this->use_gravatar = $use_gravatar; - $this->packages = $packages; - $this->attachmentURLGenerator = $attachmentURLGenerator; - $this->filterService = $filterService; - $this->entityManager = $entityManager; - $this->submitHandler = $attachmentSubmitHandler; } /** - * Returns the URL to the profile picture of the given user (in big size) - * @param User $user + * Returns the URL to the profile picture of the given user (in big size) + * * @return string */ public function getAvatarURL(User $user): string { //Check if the user has a master attachment defined (meaning he has explicitly defined a profile picture) - if ($user->getMasterPictureAttachment() !== null) { - return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_md'); + if ($user->getMasterPictureAttachment() instanceof Attachment) { + return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_md') + ?? throw new RuntimeException('Could not generate thumbnail URL'); } //If not check if gravatar is enabled (then use gravatar URL) @@ -76,8 +66,9 @@ class UserAvatarHelper public function getAvatarSmURL(User $user): string { //Check if the user has a master attachment defined (meaning he has explicitly defined a profile picture) - if ($user->getMasterPictureAttachment() !== null) { - return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_xs'); + if ($user->getMasterPictureAttachment() instanceof Attachment) { + return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_xs') + ?? throw new RuntimeException('Could not generate thumbnail URL');; } //If not check if gravatar is enabled (then use gravatar URL) @@ -88,7 +79,7 @@ class UserAvatarHelper try { //Otherwise we can serve the relative path via Asset component return $this->filterService->getUrlOfFilteredImage('/img/default_avatar.png', 'thumbnail_xs'); - } catch (\Imagine\Exception\RuntimeException $e) { + } catch (RuntimeException) { //If the filter fails, we can not serve the thumbnail and fall back to the original image and log an warning return $this->packages->getUrl('/img/default_avatar.png'); } @@ -97,8 +88,9 @@ class UserAvatarHelper public function getAvatarMdURL(User $user): string { //Check if the user has a master attachment defined (meaning he has explicitly defined a profile picture) - if ($user->getMasterPictureAttachment() !== null) { - return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_sm'); + if ($user->getMasterPictureAttachment() instanceof Attachment) { + return $this->attachmentURLGenerator->getThumbnailURL($user->getMasterPictureAttachment(), 'thumbnail_sm') + ?? throw new RuntimeException('Could not generate thumbnail URL'); } //If not check if gravatar is enabled (then use gravatar URL) @@ -109,7 +101,7 @@ class UserAvatarHelper try { //Otherwise we can serve the relative path via Asset component return $this->filterService->getUrlOfFilteredImage('/img/default_avatar.png', 'thumbnail_xs'); - } catch (\Imagine\Exception\RuntimeException $e) { + } catch (RuntimeException) { //If the filter fails, we can not serve the thumbnail and fall back to the original image and log an warning return $this->packages->getUrl('/img/default_avatar.png'); } @@ -130,28 +122,24 @@ class UserAvatarHelper private function getGravatar(User $user, int $s = 80, string $d = 'identicon', string $r = 'g'): string { $email = $user->getEmail(); - if (empty($email)) { + if ($email === null || $email === '') { $email = 'Part-DB'; } $url = 'https://www.gravatar.com/avatar/'; $url .= md5(strtolower(trim($email))); - $url .= "?s=${s}&d=${d}&r=${r}"; - return $url; + return $url . "?s=${s}&d=${d}&r=${r}"; } /** * Handles the upload of the user avatar. - * @param User $user - * @param UploadedFile $file - * @return Attachment */ public function handleAvatarUpload(User $user, UploadedFile $file): Attachment { //Determine which attachment to user //If the user already has a master attachment, we use this one - if ($user->getMasterPictureAttachment()) { + if ($user->getMasterPictureAttachment() instanceof Attachment) { $attachment = $user->getMasterPictureAttachment(); } else { //Otherwise we have to create one $attachment = new UserAttachment(); @@ -180,4 +168,4 @@ class UserAvatarHelper return $attachment; } -} \ No newline at end of file +} diff --git a/src/Services/UserSystem/UserCacheKeyGenerator.php b/src/Services/UserSystem/UserCacheKeyGenerator.php index c7c9e737..f8aec6a1 100644 --- a/src/Services/UserSystem/UserCacheKeyGenerator.php +++ b/src/Services/UserSystem/UserCacheKeyGenerator.php @@ -22,23 +22,19 @@ declare(strict_types=1); namespace App\Services\UserSystem; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\HttpFoundation\Request; use App\Entity\UserSystem\User; use Locale; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Security\Core\Security; /** * Purpose of this service is to generate a key unique for a user, to use in Cache keys and tags. */ class UserCacheKeyGenerator { - protected Security $security; - protected RequestStack $requestStack; - - public function __construct(Security $security, RequestStack $requestStack) + public function __construct(protected Security $security, protected RequestStack $requestStack) { - $this->security = $security; - $this->requestStack = $requestStack; } /** @@ -51,10 +47,10 @@ class UserCacheKeyGenerator { $request = $this->requestStack->getCurrentRequest(); //Retrieve the locale from the request, if possible, otherwise use the default locale - $locale = $request ? $request->getLocale() : Locale::getDefault(); + $locale = $request instanceof Request ? $request->getLocale() : Locale::getDefault(); //If no user was specified, use the currently used one. - if (null === $user) { + if (!$user instanceof User) { $user = $this->security->getUser(); } diff --git a/src/Twig/AttachmentExtension.php b/src/Twig/AttachmentExtension.php index b02f749d..fd93de9a 100644 --- a/src/Twig/AttachmentExtension.php +++ b/src/Twig/AttachmentExtension.php @@ -1,4 +1,7 @@ . */ - namespace App\Twig; +use App\Entity\Attachments\Attachment; use App\Services\Attachments\AttachmentURLGenerator; use App\Services\Misc\FAIconGenerator; use Twig\Extension\AbstractExtension; @@ -27,22 +30,17 @@ use Twig\TwigFunction; final class AttachmentExtension extends AbstractExtension { - protected AttachmentURLGenerator $attachmentURLGenerator; - protected FAIconGenerator $FAIconGenerator; - - public function __construct(AttachmentURLGenerator $attachmentURLGenerator, FAIconGenerator $FAIconGenerator) + public function __construct(protected AttachmentURLGenerator $attachmentURLGenerator, protected FAIconGenerator $FAIconGenerator) { - $this->attachmentURLGenerator = $attachmentURLGenerator; - $this->FAIconGenerator = $FAIconGenerator; } public function getFunctions(): array { return [ /* Returns the URL to a thumbnail of the given attachment */ - new TwigFunction('attachment_thumbnail', [$this->attachmentURLGenerator, 'getThumbnailURL']), + new TwigFunction('attachment_thumbnail', fn(Attachment $attachment, string $filter_name = 'thumbnail_sm'): ?string => $this->attachmentURLGenerator->getThumbnailURL($attachment, $filter_name)), /* Returns the font awesome icon class which is representing the given file extension */ - new TwigFunction('ext_to_fa_icon', [$this->FAIconGenerator, 'fileExtensionToFAType']), + new TwigFunction('ext_to_fa_icon', fn(string $extension): string => $this->FAIconGenerator->fileExtensionToFAType($extension)), ]; } -} \ No newline at end of file +} diff --git a/src/Twig/BarcodeExtension.php b/src/Twig/BarcodeExtension.php index 6020b65e..ae1973e3 100644 --- a/src/Twig/BarcodeExtension.php +++ b/src/Twig/BarcodeExtension.php @@ -1,4 +1,7 @@ . */ - namespace App\Twig; use Com\Tecnick\Barcode\Barcode; @@ -30,7 +32,7 @@ final class BarcodeExtension extends AbstractExtension { return [ /* Generates a barcode with the given Type and Data and returns it as an SVG represenation */ - new TwigFunction('barcode_svg', [$this, 'barcodeSVG']), + new TwigFunction('barcode_svg', fn(string $content, string $type = 'QRCODE'): string => $this->barcodeSVG($content, $type)), ]; } diff --git a/src/Twig/EntityExtension.php b/src/Twig/EntityExtension.php index 402a28b3..ee53847d 100644 --- a/src/Twig/EntityExtension.php +++ b/src/Twig/EntityExtension.php @@ -1,4 +1,7 @@ . */ - namespace App\Twig; use App\Entity\Attachments\Attachment; @@ -42,26 +44,20 @@ use Twig\Extension\AbstractExtension; use Twig\TwigFunction; use Twig\TwigTest; +/** + * @see \App\Tests\Twig\EntityExtensionTest + */ final class EntityExtension extends AbstractExtension { - protected EntityURLGenerator $entityURLGenerator; - protected TreeViewGenerator $treeBuilder; - private ElementTypeNameGenerator $nameGenerator; - - public function __construct(EntityURLGenerator $entityURLGenerator, TreeViewGenerator $treeBuilder, ElementTypeNameGenerator $elementTypeNameGenerator) + public function __construct(protected EntityURLGenerator $entityURLGenerator, protected TreeViewGenerator $treeBuilder, private readonly ElementTypeNameGenerator $nameGenerator) { - $this->entityURLGenerator = $entityURLGenerator; - $this->treeBuilder = $treeBuilder; - $this->nameGenerator = $elementTypeNameGenerator; } public function getTests(): array { return [ /* Checks if the given variable is an entitity (instance of AbstractDBElement) */ - new TwigTest('entity', static function ($var) { - return $var instanceof AbstractDBElement; - }), + new TwigTest('entity', static fn($var) => $var instanceof AbstractDBElement), ]; } @@ -69,16 +65,16 @@ final class EntityExtension extends AbstractExtension { return [ /* Returns a string representation of the given entity */ - new TwigFunction('entity_type', [$this, 'getEntityType']), + new TwigFunction('entity_type', fn(object $entity): ?string => $this->getEntityType($entity)), /* Returns the URL to the given entity */ - new TwigFunction('entity_url', [$this, 'generateEntityURL']), + new TwigFunction('entity_url', fn(AbstractDBElement $entity, string $method = 'info'): string => $this->generateEntityURL($entity, $method)), /* Returns the URL to the given entity in timetravel mode */ - new TwigFunction('timetravel_url', [$this, 'timeTravelURL']), + new TwigFunction('timetravel_url', fn(AbstractDBElement $element, \DateTimeInterface $dateTime): ?string => $this->timeTravelURL($element, $dateTime)), /* Generates a JSON array of the given tree */ - new TwigFunction('tree_data', [$this, 'treeData']), + new TwigFunction('tree_data', fn(AbstractDBElement $element, string $type = 'newEdit'): string => $this->treeData($element, $type)), /* Gets a human readable label for the type of the given entity */ - new TwigFunction('entity_type_label', [$this->nameGenerator, 'getLocalizedTypeLabel']), + new TwigFunction('entity_type_label', fn(object|string $entity): string => $this->nameGenerator->getLocalizedTypeLabel($entity)), ]; } @@ -86,14 +82,14 @@ final class EntityExtension extends AbstractExtension { try { return $this->entityURLGenerator->timeTravelURL($element, $dateTime); - } catch (EntityNotSupportedException $e) { + } catch (EntityNotSupportedException) { return null; } } public function treeData(AbstractDBElement $element, string $type = 'newEdit'): string { - $tree = $this->treeBuilder->getTreeView(get_class($element), null, $type, $element); + $tree = $this->treeBuilder->getTreeView($element::class, null, $type, $element); return json_encode($tree, JSON_THROW_ON_ERROR); } @@ -127,6 +123,6 @@ final class EntityExtension extends AbstractExtension } } - return false; + return null; } -} \ No newline at end of file +} diff --git a/src/Twig/FormatExtension.php b/src/Twig/FormatExtension.php index 775a3fe4..76628ccd 100644 --- a/src/Twig/FormatExtension.php +++ b/src/Twig/FormatExtension.php @@ -34,37 +34,26 @@ use Twig\TwigFilter; final class FormatExtension extends AbstractExtension { - protected MarkdownParser $markdownParser; - protected MoneyFormatter $moneyFormatter; - protected SIFormatter $siformatter; - protected AmountFormatter $amountFormatter; - - - public function __construct(MarkdownParser $markdownParser, MoneyFormatter $moneyFormatter, - SIFormatter $SIFormatter, AmountFormatter $amountFormatter) + public function __construct(protected MarkdownParser $markdownParser, protected MoneyFormatter $moneyFormatter, protected SIFormatter $siformatter, protected AmountFormatter $amountFormatter) { - $this->markdownParser = $markdownParser; - $this->moneyFormatter = $moneyFormatter; - $this->siformatter = $SIFormatter; - $this->amountFormatter = $amountFormatter; } public function getFilters(): array { return [ /* Mark the given text as markdown, which will be rendered in the browser */ - new TwigFilter('format_markdown', [$this->markdownParser, 'markForRendering'], [ + new TwigFilter('format_markdown', fn(string $markdown, bool $inline_mode = false): string => $this->markdownParser->markForRendering($markdown, $inline_mode), [ 'pre_escape' => 'html', 'is_safe' => ['html'], ]), /* Format the given amount as money, using a given currency */ - new TwigFilter('format_money', [$this, 'formatCurrency']), + new TwigFilter('format_money', fn($amount, ?Currency $currency = null, int $decimals = 5): string => $this->formatCurrency($amount, $currency, $decimals)), /* Format the given number using SI prefixes and the given unit (string) */ - new TwigFilter('format_si', [$this, 'siFormat']), + new TwigFilter('format_si', fn($value, $unit, $decimals = 2, bool $show_all_digits = false): string => $this->siFormat($value, $unit, $decimals, $show_all_digits)), /** Format the given amount using the given MeasurementUnit */ - new TwigFilter('format_amount', [$this, 'amountFormat']), + new TwigFilter('format_amount', fn($value, ?MeasurementUnit $unit, array $options = []): string => $this->amountFormat($value, $unit, $options)), /** Format the given number of bytes as human-readable number */ - new TwigFilter('format_bytes', [$this, 'formatBytes']), + new TwigFilter('format_bytes', fn(int $bytes, int $precision = 2): string => $this->formatBytes($bytes, $precision)), ]; } @@ -89,8 +78,6 @@ final class FormatExtension extends AbstractExtension /** * @param $bytes - * @param int $precision - * @return string */ public function formatBytes(int $bytes, int $precision = 2): string { diff --git a/src/Twig/LogExtension.php b/src/Twig/LogExtension.php index a56b9275..34dad988 100644 --- a/src/Twig/LogExtension.php +++ b/src/Twig/LogExtension.php @@ -1,4 +1,7 @@ . */ - namespace App\Twig; +use App\Entity\LogSystem\AbstractLogEntry; use App\Services\LogSystem\LogDataFormatter; use App\Services\LogSystem\LogDiffFormatter; use Twig\Extension\AbstractExtension; @@ -28,20 +31,15 @@ use Twig\TwigFunction; final class LogExtension extends AbstractExtension { - private LogDataFormatter $logDataFormatter; - private LogDiffFormatter $logDiffFormatter; - - public function __construct(LogDataFormatter $logDataFormatter, LogDiffFormatter $logDiffFormatter) + public function __construct(private readonly LogDataFormatter $logDataFormatter, private readonly LogDiffFormatter $logDiffFormatter) { - $this->logDataFormatter = $logDataFormatter; - $this->logDiffFormatter = $logDiffFormatter; } - public function getFunctions() + public function getFunctions(): array { return [ - new TwigFunction('format_log_data', [$this->logDataFormatter, 'formatData'], ['is_safe' => ['html']]), - new TwigFunction('format_log_diff', [$this->logDiffFormatter, 'formatDiff'], ['is_safe' => ['html']]), + new TwigFunction('format_log_data', fn($data, AbstractLogEntry $logEntry, string $fieldName): string => $this->logDataFormatter->formatData($data, $logEntry, $fieldName), ['is_safe' => ['html']]), + new TwigFunction('format_log_diff', fn($old_data, $new_data): string => $this->logDiffFormatter->formatDiff($old_data, $new_data), ['is_safe' => ['html']]), ]; } -} \ No newline at end of file +} diff --git a/src/Twig/MiscExtension.php b/src/Twig/MiscExtension.php index 6181e9cd..1edef7a3 100644 --- a/src/Twig/MiscExtension.php +++ b/src/Twig/MiscExtension.php @@ -1,4 +1,7 @@ . */ - namespace App\Twig; +use Twig\TwigFunction; use App\Services\LogSystem\EventCommentNeededHelper; use Twig\Extension\AbstractExtension; final class MiscExtension extends AbstractExtension { - private EventCommentNeededHelper $eventCommentNeededHelper; - - public function __construct(EventCommentNeededHelper $eventCommentNeededHelper) + public function __construct(private readonly EventCommentNeededHelper $eventCommentNeededHelper) { - $this->eventCommentNeededHelper = $eventCommentNeededHelper; } public function getFunctions(): array { return [ - new \Twig\TwigFunction('event_comment_needed', + new TwigFunction('event_comment_needed', fn(string $operation_type) => $this->eventCommentNeededHelper->isCommentNeeded($operation_type) ), ]; } -} \ No newline at end of file +} diff --git a/src/Twig/Sandbox/InheritanceSecurityPolicy.php b/src/Twig/Sandbox/InheritanceSecurityPolicy.php index b70be7a5..db1cd2b5 100644 --- a/src/Twig/Sandbox/InheritanceSecurityPolicy.php +++ b/src/Twig/Sandbox/InheritanceSecurityPolicy.php @@ -35,19 +35,11 @@ use function is_array; */ final class InheritanceSecurityPolicy implements SecurityPolicyInterface { - private array $allowedTags; - private array $allowedFilters; private array $allowedMethods; - private array $allowedProperties; - private array $allowedFunctions; - public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedMethods = [], array $allowedProperties = [], array $allowedFunctions = []) + public function __construct(private array $allowedTags = [], private array $allowedFilters = [], array $allowedMethods = [], private array $allowedProperties = [], private array $allowedFunctions = []) { - $this->allowedTags = $allowedTags; - $this->allowedFilters = $allowedFilters; $this->setAllowedMethods($allowedMethods); - $this->allowedProperties = $allowedProperties; - $this->allowedFunctions = $allowedFunctions; } public function setAllowedTags(array $tags): void @@ -65,7 +57,7 @@ final class InheritanceSecurityPolicy implements SecurityPolicyInterface $this->allowedMethods = []; foreach ($methods as $class => $m) { $this->allowedMethods[$class] = array_map( - static function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, is_array($m) ? $m : [$m]); + static fn($value): string => strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), is_array($m) ? $m : [$m]); } } @@ -120,7 +112,7 @@ final class InheritanceSecurityPolicy implements SecurityPolicyInterface } if (!$allowed) { - $class = get_class($obj); + $class = $obj::class; throw new SecurityNotAllowedMethodError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method); } @@ -141,7 +133,7 @@ final class InheritanceSecurityPolicy implements SecurityPolicyInterface } if (!$allowed) { - $class = get_class($obj); + $class = $obj::class; throw new SecurityNotAllowedPropertyError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property); } diff --git a/src/Twig/TwigCoreExtension.php b/src/Twig/TwigCoreExtension.php index 6b12299a..1cb7f1dc 100644 --- a/src/Twig/TwigCoreExtension.php +++ b/src/Twig/TwigCoreExtension.php @@ -1,4 +1,7 @@ . */ - namespace App\Twig; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; +use Twig\TwigFunction; use Twig\TwigTest; /** * The functionalities here extend the Twig with some core functions, which are independently of Part-DB. + * @see \App\Tests\Twig\TwigCoreExtensionTest */ final class TwigCoreExtension extends AbstractExtension { - protected ObjectNormalizer $objectNormalizer; - - public function __construct(ObjectNormalizer $objectNormalizer) + public function __construct(protected ObjectNormalizer $objectNormalizer) { - $this->objectNormalizer = $objectNormalizer; + } + + public function getFunctions(): array + { + return [ + /* Returns the enum cases as values */ + new TwigFunction('enum_cases', [$this, 'getEnumCases']), + ]; } public function getTests(): array @@ -43,30 +52,35 @@ final class TwigCoreExtension extends AbstractExtension /* * Checks if a given variable is an instance of a given class. E.g. ` x is instanceof('App\Entity\Parts\Part')` */ - new TwigTest('instanceof', static function ($var, $instance) { - return $var instanceof $instance; - }), + new TwigTest('instanceof', static fn($var, $instance) => $var instanceof $instance), /* Checks if a given variable is an object. E.g. `x is object` */ - new TwigTest('object', static function ($var) { - return is_object($var); - }), + new TwigTest('object', static fn($var): bool => is_object($var)), ]; } + /** + * @param string $enum_class + * @phpstan-param class-string $enum_class + */ + public function getEnumCases(string $enum_class): array + { + if (!enum_exists($enum_class)) { + throw new \InvalidArgumentException(sprintf('The given class "%s" is not an enum!', $enum_class)); + } + + return ($enum_class)::cases(); + } + public function getFilters(): array { return [ /* Converts the given object to an array representation of the public/accessible properties */ - new TwigFilter('to_array', [$this, 'toArray']), + new TwigFilter('to_array', fn($object) => $this->toArray($object)), ]; } - public function toArray($object) + public function toArray(object|array $object): array { - if(! is_object($object) && ! is_array($object)) { - throw new \InvalidArgumentException('The given variable is not an object or array!'); - } - //If it is already an array, we can just return it if(is_array($object)) { return $object; @@ -74,4 +88,4 @@ final class TwigCoreExtension extends AbstractExtension return $this->objectNormalizer->normalize($object, null); } -} \ No newline at end of file +} diff --git a/src/Twig/UserExtension.php b/src/Twig/UserExtension.php index 3b0ec75c..be5d3c41 100644 --- a/src/Twig/UserExtension.php +++ b/src/Twig/UserExtension.php @@ -41,6 +41,8 @@ declare(strict_types=1); namespace App\Twig; +use App\Entity\Base\AbstractDBElement; +use App\Entity\UserSystem\User; use App\Entity\LogSystem\AbstractLogEntry; use App\Repository\LogEntryRepository; use Doctrine\ORM\EntityManagerInterface; @@ -48,9 +50,12 @@ use Twig\Extension\AbstractExtension; use Twig\TwigFilter; use Twig\TwigFunction; +/** + * @see \App\Tests\Twig\UserExtensionTest + */ final class UserExtension extends AbstractExtension { - private LogEntryRepository $repo; + private readonly LogEntryRepository $repo; public function __construct(EntityManagerInterface $em) { @@ -60,7 +65,7 @@ final class UserExtension extends AbstractExtension public function getFilters(): array { return [ - new TwigFilter('remove_locale_from_path', [$this, 'removeLocaleFromPath']), + new TwigFilter('remove_locale_from_path', fn(string $path): string => $this->removeLocaleFromPath($path)), ]; } @@ -68,9 +73,9 @@ final class UserExtension extends AbstractExtension { return [ /* Returns the user which has edited the given entity the last time. */ - new TwigFunction('last_editing_user', [$this->repo, 'getLastEditingUser']), + new TwigFunction('last_editing_user', fn(AbstractDBElement $element): ?User => $this->repo->getLastEditingUser($element)), /* Returns the user which has created the given entity. */ - new TwigFunction('creating_user', [$this->repo, 'getCreatingUser']), + new TwigFunction('creating_user', fn(AbstractDBElement $element): ?User => $this->repo->getCreatingUser($element)), ]; } diff --git a/src/Validator/Constraints/BigDecimal/BigDecimalGreaterThanValidator.php b/src/Validator/Constraints/BigDecimal/BigDecimalGreaterThanValidator.php index 62231ea8..76acb25a 100644 --- a/src/Validator/Constraints/BigDecimal/BigDecimalGreaterThanValidator.php +++ b/src/Validator/Constraints/BigDecimal/BigDecimalGreaterThanValidator.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints\BigDecimal; use Brick\Math\BigDecimal; diff --git a/src/Validator/Constraints/BigDecimal/BigDecimalGreaterThenOrEqualValidator.php b/src/Validator/Constraints/BigDecimal/BigDecimalGreaterThenOrEqualValidator.php index 3e4c8444..b6df06d0 100644 --- a/src/Validator/Constraints/BigDecimal/BigDecimalGreaterThenOrEqualValidator.php +++ b/src/Validator/Constraints/BigDecimal/BigDecimalGreaterThenOrEqualValidator.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints\BigDecimal; use Brick\Math\BigDecimal; diff --git a/src/Validator/Constraints/BigDecimal/BigDecimalPositive.php b/src/Validator/Constraints/BigDecimal/BigDecimalPositive.php index bb4e61eb..0e7e755f 100644 --- a/src/Validator/Constraints/BigDecimal/BigDecimalPositive.php +++ b/src/Validator/Constraints/BigDecimal/BigDecimalPositive.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints\BigDecimal; -use Symfony\Component\Validator\Constraints\GreaterThan; +use Symfony\Component\Validator\Constraints\Positive; -/** - * @Annotation - * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) - * - * @author Jan Schädlich - */ -class BigDecimalPositive extends GreaterThan +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class BigDecimalPositive extends Positive { - use BigNumberConstraintTrait; - - public $message = 'This value should be positive.'; - - public function __construct($options = null) - { - parent::__construct($this->configureNumberConstraintOptions($options)); - } - public function validatedBy(): string { return BigDecimalGreaterThanValidator::class; diff --git a/src/Validator/Constraints/BigDecimal/BigDecimalPositiveOrZero.php b/src/Validator/Constraints/BigDecimal/BigDecimalPositiveOrZero.php index 1e6558a2..408cd582 100644 --- a/src/Validator/Constraints/BigDecimal/BigDecimalPositiveOrZero.php +++ b/src/Validator/Constraints/BigDecimal/BigDecimalPositiveOrZero.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints\BigDecimal; -use Symfony\Component\Validator\Constraints\GreaterThanOrEqual; +use Symfony\Component\Validator\Constraints\PositiveOrZero; -/** - * @Annotation - * @Target({"PROPERTY", "METHOD", "ANNOTATION"}) - * - * @author Jan Schädlich - */ -class BigDecimalPositiveOrZero extends GreaterThanOrEqual +#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +class BigDecimalPositiveOrZero extends PositiveOrZero { - use BigNumberConstraintTrait; - - public $message = 'This value should be either positive or zero.'; - - public function __construct($options = null) - { - parent::__construct($this->configureNumberConstraintOptions($options)); - } - public function validatedBy(): string { return BigDecimalGreaterThenOrEqualValidator::class; diff --git a/src/Validator/Constraints/BigDecimal/BigNumberConstraintTrait.php b/src/Validator/Constraints/BigDecimal/BigNumberConstraintTrait.php deleted file mode 100644 index a9858730..00000000 --- a/src/Validator/Constraints/BigDecimal/BigNumberConstraintTrait.php +++ /dev/null @@ -1,49 +0,0 @@ -. - */ - -namespace App\Validator\Constraints\BigDecimal; - -use Symfony\Component\Validator\Exception\ConstraintDefinitionException; - -use function is_array; - -trait BigNumberConstraintTrait -{ - private function configureNumberConstraintOptions($options): array - { - if (null === $options) { - $options = []; - } elseif (!is_array($options)) { - $options = [$this->getDefaultOption() => $options]; - } - - if (isset($options['propertyPath'])) { - throw new ConstraintDefinitionException(sprintf('The "propertyPath" option of the "%s" constraint cannot be set.', static::class)); - } - - if (isset($options['value'])) { - throw new ConstraintDefinitionException(sprintf('The "value" option of the "%s" constraint cannot be set.', static::class)); - } - - $options['value'] = 0; - - return $options; - } -} \ No newline at end of file diff --git a/src/Validator/Constraints/Misc/ValidRange.php b/src/Validator/Constraints/Misc/ValidRange.php index 15781498..680eb04d 100644 --- a/src/Validator/Constraints/Misc/ValidRange.php +++ b/src/Validator/Constraints/Misc/ValidRange.php @@ -43,9 +43,7 @@ namespace App\Validator\Constraints\Misc; use Symfony\Component\Validator\Constraint; -/** - * @Annotation - */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] class ValidRange extends Constraint { public string $message = 'validator.invalid_range'; diff --git a/src/Validator/Constraints/Misc/ValidRangeValidator.php b/src/Validator/Constraints/Misc/ValidRangeValidator.php index 8385cc92..8bc5af0c 100644 --- a/src/Validator/Constraints/Misc/ValidRangeValidator.php +++ b/src/Validator/Constraints/Misc/ValidRangeValidator.php @@ -49,11 +49,8 @@ use Symfony\Component\Validator\Exception\UnexpectedValueException; class ValidRangeValidator extends ConstraintValidator { - protected RangeParser $rangeParser; - - public function __construct(RangeParser $rangeParser) + public function __construct(protected RangeParser $rangeParser) { - $this->rangeParser = $rangeParser; } public function validate($value, Constraint $constraint): void diff --git a/src/Validator/Constraints/NoLockout.php b/src/Validator/Constraints/NoLockout.php index 323dddb4..30eb770f 100644 --- a/src/Validator/Constraints/NoLockout.php +++ b/src/Validator/Constraints/NoLockout.php @@ -26,10 +26,14 @@ use Symfony\Component\Validator\Constraint; /** * This constraint restricts a user in that way that it can not lock itself out of the user system. - * - * @Annotation */ +#[\Attribute(\Attribute::TARGET_CLASS)] class NoLockout extends Constraint { public string $message = 'validator.noLockout'; + + public function getTargets(): string|array + { + return [self::CLASS_CONSTRAINT]; + } } diff --git a/src/Validator/Constraints/NoLockoutValidator.php b/src/Validator/Constraints/NoLockoutValidator.php index fece2852..9d51d81e 100644 --- a/src/Validator/Constraints/NoLockoutValidator.php +++ b/src/Validator/Constraints/NoLockoutValidator.php @@ -22,28 +22,20 @@ declare(strict_types=1); namespace App\Validator\Constraints; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Security\Core\User\UserInterface; use App\Entity\UserSystem\Group; use App\Entity\UserSystem\User; use App\Services\UserSystem\PermissionManager; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Form\Exception\UnexpectedTypeException; -use Symfony\Component\Security\Core\Security; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; class NoLockoutValidator extends ConstraintValidator { - protected PermissionManager $resolver; - protected array $perm_structure; - protected Security $security; - protected EntityManagerInterface $entityManager; - - public function __construct(PermissionManager $resolver, Security $security, EntityManagerInterface $entityManager) + public function __construct(protected PermissionManager $resolver, protected Security $security, protected EntityManagerInterface $entityManager) { - $this->resolver = $resolver; - $this->perm_structure = $resolver->getPermissionStructure(); - $this->security = $security; - $this->entityManager = $entityManager; } /** @@ -64,18 +56,20 @@ class NoLockoutValidator extends ConstraintValidator if ($perm_holder instanceof User || $perm_holder instanceof Group) { $user = $this->security->getUser(); - if (null === $user) { + if (!$user instanceof UserInterface) { $user = $this->entityManager->getRepository(User::class)->getAnonymousUser(); } //Check if the change_permission permission has changed from allow to disallow - if (($user instanceof User) && false === ($this->resolver->inherit( + if (($user instanceof User) && !($this->resolver->inherit( $user, 'users', 'edit_permissions' ) ?? false)) { $this->context->addViolation($constraint->message); } + } else { + throw new \LogicException('The NoLockout constraint can only be used on User or Group objects.'); } } } diff --git a/src/Validator/Constraints/NoneOfItsChildren.php b/src/Validator/Constraints/NoneOfItsChildren.php index b8e92faa..8f1e059a 100644 --- a/src/Validator/Constraints/NoneOfItsChildren.php +++ b/src/Validator/Constraints/NoneOfItsChildren.php @@ -27,9 +27,8 @@ use Symfony\Component\Validator\Constraint; /** * Constraints the parent property on StructuralDBElement objects in the way, that neither the object self nor any * of its children can be assigned. - * - * @Annotation */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] class NoneOfItsChildren extends Constraint { /** diff --git a/src/Validator/Constraints/ProjectSystem/ValidProjectBuildRequest.php b/src/Validator/Constraints/ProjectSystem/ValidProjectBuildRequest.php index b0c99947..1e9ac834 100644 --- a/src/Validator/Constraints/ProjectSystem/ValidProjectBuildRequest.php +++ b/src/Validator/Constraints/ProjectSystem/ValidProjectBuildRequest.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints\ProjectSystem; use Symfony\Component\Validator\Constraint; /** * This constraint checks that the given ProjectBuildRequest is valid. - * - * @Annotation */ +#[\Attribute(\Attribute::TARGET_CLASS)] class ValidProjectBuildRequest extends Constraint { public function getTargets(): string { return self::CLASS_CONSTRAINT; } -} \ No newline at end of file +} diff --git a/src/Validator/Constraints/ProjectSystem/ValidProjectBuildRequestValidator.php b/src/Validator/Constraints/ProjectSystem/ValidProjectBuildRequestValidator.php index 03a9b81f..e5de07d2 100644 --- a/src/Validator/Constraints/ProjectSystem/ValidProjectBuildRequestValidator.php +++ b/src/Validator/Constraints/ProjectSystem/ValidProjectBuildRequestValidator.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints\ProjectSystem; use App\Entity\Parts\PartLot; @@ -36,7 +38,7 @@ class ValidProjectBuildRequestValidator extends ConstraintValidator ->setParameter('{{ lot }}', $partLot->getName()); } - public function validate($value, Constraint $constraint) + public function validate($value, Constraint $constraint): void { if (!$constraint instanceof ValidProjectBuildRequest) { throw new UnexpectedTypeException($constraint, ValidProjectBuildRequest::class); @@ -79,4 +81,4 @@ class ValidProjectBuildRequestValidator extends ConstraintValidator } } } -} \ No newline at end of file +} diff --git a/src/Validator/Constraints/Selectable.php b/src/Validator/Constraints/Selectable.php index 20a51aad..f65cb685 100644 --- a/src/Validator/Constraints/Selectable.php +++ b/src/Validator/Constraints/Selectable.php @@ -27,9 +27,8 @@ use Symfony\Component\Validator\Constraint; /** * If a property is marked with this constraint, the choosen value (of type StructuralDBElement) * must NOT be marked as not selectable. - * - * @Annotation */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] class Selectable extends Constraint { public $message = 'validator.isSelectable'; diff --git a/src/Validator/Constraints/UrlOrBuiltin.php b/src/Validator/Constraints/UrlOrBuiltin.php index 20b71871..ceec5d07 100644 --- a/src/Validator/Constraints/UrlOrBuiltin.php +++ b/src/Validator/Constraints/UrlOrBuiltin.php @@ -27,9 +27,8 @@ use Symfony\Component\Validator\Constraints\Url; /** * Constraints the field that way that the content is either an url or a path to a builtin ressource (like %FOOTPRINTS%). - * - * @Annotation */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] class UrlOrBuiltin extends Url { /** diff --git a/src/Validator/Constraints/UrlOrBuiltinValidator.php b/src/Validator/Constraints/UrlOrBuiltinValidator.php index b8ad3b6a..af498d2a 100644 --- a/src/Validator/Constraints/UrlOrBuiltinValidator.php +++ b/src/Validator/Constraints/UrlOrBuiltinValidator.php @@ -57,7 +57,7 @@ class UrlOrBuiltinValidator extends UrlValidator //After the %PLACEHOLDER% comes a slash, so we can check if we have a placholder via explode $tmp = explode('/', $value); //Builtins must have a %PLACEHOLDER% construction - if (in_array($tmp[0], $constraint->allowed_placeholders, false)) { + if (in_array($tmp[0], $constraint->allowed_placeholders, true)) { return; } diff --git a/src/Validator/Constraints/ValidFileFilter.php b/src/Validator/Constraints/ValidFileFilter.php index 8a7b70d0..d962c0ea 100644 --- a/src/Validator/Constraints/ValidFileFilter.php +++ b/src/Validator/Constraints/ValidFileFilter.php @@ -24,9 +24,7 @@ namespace App\Validator\Constraints; use Symfony\Component\Validator\Constraint; -/** - * @Annotation - */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] class ValidFileFilter extends Constraint { } diff --git a/src/Validator/Constraints/ValidFileFilterValidator.php b/src/Validator/Constraints/ValidFileFilterValidator.php index ccce30ce..d591a968 100644 --- a/src/Validator/Constraints/ValidFileFilterValidator.php +++ b/src/Validator/Constraints/ValidFileFilterValidator.php @@ -32,11 +32,8 @@ use function is_string; class ValidFileFilterValidator extends ConstraintValidator { - protected FileTypeFilterTools $filterTools; - - public function __construct(FileTypeFilterTools $filterTools) + public function __construct(protected FileTypeFilterTools $filterTools) { - $this->filterTools = $filterTools; } /** diff --git a/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php b/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php index bb66f395..276201f3 100644 --- a/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php +++ b/src/Validator/Constraints/ValidGoogleAuthCodeValidator.php @@ -36,11 +36,8 @@ use function strlen; class ValidGoogleAuthCodeValidator extends ConstraintValidator { - protected GoogleAuthenticatorInterface $googleAuthenticator; - - public function __construct(GoogleAuthenticatorInterface $googleAuthenticator) + public function __construct(protected GoogleAuthenticatorInterface $googleAuthenticator) { - $this->googleAuthenticator = $googleAuthenticator; } public function validate($value, Constraint $constraint): void diff --git a/src/Validator/Constraints/ValidPartLot.php b/src/Validator/Constraints/ValidPartLot.php index 3b9658ac..a82c6a10 100644 --- a/src/Validator/Constraints/ValidPartLot.php +++ b/src/Validator/Constraints/ValidPartLot.php @@ -27,9 +27,8 @@ use Symfony\Component\Validator\Constraint; /** * A constraint "dummy" to validate the PartLot. * We need to access services in our Validator, so we can not use a simple callback on PartLot. - * - * @Annotation */ +#[\Attribute(\Attribute::TARGET_CLASS)] class ValidPartLot extends Constraint { public function getTargets(): string diff --git a/src/Validator/Constraints/ValidPartLotValidator.php b/src/Validator/Constraints/ValidPartLotValidator.php index d77ecd0e..4f988362 100644 --- a/src/Validator/Constraints/ValidPartLotValidator.php +++ b/src/Validator/Constraints/ValidPartLotValidator.php @@ -32,11 +32,8 @@ use Symfony\Component\Validator\ConstraintValidator; class ValidPartLotValidator extends ConstraintValidator { - protected EntityManagerInterface $em; - - public function __construct(EntityManagerInterface $em) + public function __construct(protected EntityManagerInterface $em) { - $this->em = $em; } /** @@ -56,7 +53,7 @@ class ValidPartLotValidator extends ConstraintValidator } //We can only validate the values if we know the storelocation - if ($value->getStorageLocation()) { + if ($value->getStorageLocation() instanceof Storelocation) { $repo = $this->em->getRepository(Storelocation::class); //We can only determine associated parts, if the part have an ID //When the storage location is new (no ID), we can just assume there are no other parts diff --git a/src/Validator/Constraints/ValidPermission.php b/src/Validator/Constraints/ValidPermission.php index 58a4ad2c..4d05f6cf 100644 --- a/src/Validator/Constraints/ValidPermission.php +++ b/src/Validator/Constraints/ValidPermission.php @@ -28,8 +28,8 @@ use Symfony\Component\Validator\Constraint; * A PermissionEmbed object with this annotation will be checked with ValidPermissionValidator. * That means the alsoSet values of the permission operations are set. * - * @Annotation */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] class ValidPermission extends Constraint { } diff --git a/src/Validator/Constraints/ValidPermissionValidator.php b/src/Validator/Constraints/ValidPermissionValidator.php index 9b31048f..c0004e6c 100644 --- a/src/Validator/Constraints/ValidPermissionValidator.php +++ b/src/Validator/Constraints/ValidPermissionValidator.php @@ -30,12 +30,8 @@ use Symfony\Component\Validator\ConstraintValidator; class ValidPermissionValidator extends ConstraintValidator { - protected PermissionManager $resolver; - protected array $perm_structure; - - public function __construct(PermissionManager $resolver) + public function __construct(protected PermissionManager $resolver) { - $this->resolver = $resolver; } /** diff --git a/src/Validator/Constraints/ValidTheme.php b/src/Validator/Constraints/ValidTheme.php index 70a32a20..92a19f5a 100644 --- a/src/Validator/Constraints/ValidTheme.php +++ b/src/Validator/Constraints/ValidTheme.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints; use Symfony\Component\Validator\Constraint; /** * A constraint to validate the theme setting of the user. - * @Annotation */ +#[\Attribute(\Attribute::TARGET_PROPERTY)] class ValidTheme extends Constraint { public string $message = 'validator.selected_theme_is_invalid'; -} \ No newline at end of file +} diff --git a/src/Validator/Constraints/ValidThemeValidator.php b/src/Validator/Constraints/ValidThemeValidator.php index ec437b87..5d222934 100644 --- a/src/Validator/Constraints/ValidThemeValidator.php +++ b/src/Validator/Constraints/ValidThemeValidator.php @@ -1,4 +1,7 @@ . */ - namespace App\Validator\Constraints; use Symfony\Component\Validator\Constraint; @@ -26,14 +28,11 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException; class ValidThemeValidator extends ConstraintValidator { - private array $available_themes; - - public function __construct(array $available_themes) + public function __construct(private readonly array $available_themes) { - $this->available_themes = $available_themes; } - public function validate($value, Constraint $constraint) + public function validate($value, Constraint $constraint): void { if (!$constraint instanceof ValidTheme) { throw new UnexpectedTypeException($constraint, ValidTheme::class); @@ -51,4 +50,4 @@ class ValidThemeValidator extends ConstraintValidator ->addViolation(); } } -} \ No newline at end of file +} diff --git a/symfony.lock b/symfony.lock index a4bb23ff..cdd153ae 100644 --- a/symfony.lock +++ b/symfony.lock @@ -45,16 +45,14 @@ "version": "v0.1.1" }, "doctrine/annotations": { - "version": "1.0", + "version": "1.14", "recipe": { "repo": "github.com/symfony/recipes", - "branch": "master", - "version": "1.0", - "ref": "a2759dd6123694c8d901d0ec80006e044c2e6457" + "branch": "main", + "version": "1.10", + "ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05" }, - "files": [ - "./config/routes/annotations.yaml" - ] + "files": [] }, "doctrine/cache": { "version": "v1.8.0" @@ -75,12 +73,12 @@ "version": "v0.5.3" }, "doctrine/doctrine-bundle": { - "version": "2.8", + "version": "2.10", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "2.4", - "ref": "013b823e7fee65890b23e40f31e6667a1ac519ac" + "version": "2.10", + "ref": "f0d8c9a4da17815830aac0d63e153a940ae176bb" }, "files": [ "config/packages/doctrine.yaml", @@ -173,9 +171,6 @@ "gregwar/captcha-bundle": { "version": "v2.0.6" }, - "hslavich/oneloginsaml-bundle": { - "version": "v2.10.0" - }, "imagine/imagine": { "version": "1.2.2" }, @@ -207,6 +202,9 @@ "monolog/monolog": { "version": "1.24.0" }, + "nbgrp/onelogin-saml-bundle": { + "version": "v1.3.2" + }, "nelmio/security-bundle": { "version": "2.4", "recipe": { @@ -262,7 +260,16 @@ "version": "v0.3.3" }, "php-http/discovery": { - "version": "1.7.0" + "version": "1.18", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.18", + "ref": "f45b5dd173a27873ab19f5e3180b2f661c21de02" + }, + "files": [ + "./config/packages/http_discovery.yaml" + ] }, "php-http/httplug": { "version": "v2.0.0" @@ -364,33 +371,21 @@ "version": "8.3.0" }, "scheb/2fa-bundle": { - "version": "5.13", + "version": "6.8", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "5.0", - "ref": "0a83961ef50ff91812b229a6f0caf28431d94aec" + "version": "6.0", + "ref": "1e6f68089146853a790b5da9946fc5974f6fcd49" }, "files": [ - "./config/packages/scheb_2fa.yaml", - "./config/routes/scheb_2fa.yaml" + "config/packages/scheb_2fa.yaml", + "config/routes/scheb_2fa.yaml" ] }, "sebastian/diff": { "version": "3.0.2" }, - "sensio/framework-extra-bundle": { - "version": "5.2", - "recipe": { - "repo": "github.com/symfony/recipes", - "branch": "master", - "version": "5.2", - "ref": "fb7e19da7f013d0d422fa9bce16f5c510e27609b" - }, - "files": [ - "./config/packages/sensio_framework_extra.yaml" - ] - }, "shivas/versioning-bundle": { "version": "3.1.3" }, @@ -500,12 +495,12 @@ "version": "v4.2.3" }, "symfony/framework-bundle": { - "version": "5.4", + "version": "6.2", "recipe": { "repo": "github.com/symfony/recipes", - "branch": "master", - "version": "5.4", - "ref": "3cd216a4d007b78d8554d44a5b1c0a446dab24fb" + "branch": "main", + "version": "6.2", + "ref": "af47254c5e4cd543e6af3e4508298ffebbdaddd3" }, "files": [ "config/packages/cache.yaml", @@ -533,18 +528,6 @@ "symfony/intl": { "version": "v4.2.3" }, - "symfony/lock": { - "version": "5.4", - "recipe": { - "repo": "github.com/symfony/recipes", - "branch": "main", - "version": "5.2", - "ref": "8e937ff2b4735d110af1770f242c1107fdab4c8e" - }, - "files": [ - "./config/packages/lock.yaml" - ] - }, "symfony/mailer": { "version": "5.4", "recipe": { @@ -626,15 +609,9 @@ "symfony/polyfill-php72": { "version": "v1.10.0" }, - "symfony/polyfill-php73": { - "version": "v1.11.0" - }, "symfony/polyfill-php80": { "version": "v1.17.0" }, - "symfony/polyfill-php81": { - "version": "v1.23.0" - }, "symfony/process": { "version": "v4.2.3" }, @@ -648,12 +625,12 @@ "version": "v5.2.1" }, "symfony/routing": { - "version": "5.4", + "version": "6.2", "recipe": { "repo": "github.com/symfony/recipes", - "branch": "master", - "version": "5.3", - "ref": "85de1d8ae45b284c3c84b668171d2615049e698f" + "branch": "main", + "version": "6.2", + "ref": "e0a11b4ccb8c9e70b574ff5ad3dfdcd41dec5aa6" }, "files": [ "config/packages/routing.yaml", @@ -664,12 +641,12 @@ "version": "v5.3.4" }, "symfony/security-bundle": { - "version": "5.4", + "version": "6.2", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", - "version": "5.3", - "ref": "98f1f2b0d635908c2b40f3675da2d23b1a069d30" + "version": "6.0", + "ref": "8a5b112826f7d3d5b07027f93786ae11a1c7de48" }, "files": [ "config/packages/security.yaml" @@ -681,9 +658,6 @@ "symfony/security-csrf": { "version": "v4.2.3" }, - "symfony/security-guard": { - "version": "v4.2.3" - }, "symfony/security-http": { "version": "v4.2.3" }, @@ -693,6 +667,20 @@ "symfony/service-contracts": { "version": "v1.1.5" }, + "symfony/stimulus-bundle": { + "version": "2.9", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.9", + "ref": "05c45071c7ecacc1e48f94bc43c1f8d4405fb2b2" + }, + "files": [ + "./assets/bootstrap.js", + "./assets/controllers.json", + "./assets/controllers/hello_controller.js" + ] + }, "symfony/stopwatch": { "version": "v4.2.3" }, @@ -719,18 +707,30 @@ "version": "v4.2.3" }, "symfony/twig-bundle": { - "version": "5.4", + "version": "6.3", "recipe": { "repo": "github.com/symfony/recipes", - "branch": "master", - "version": "5.4", - "ref": "bb2178c57eee79e6be0b297aa96fc0c0def81387" + "branch": "main", + "version": "6.3", + "ref": "b7772eb20e92f3fb4d4fe756e7505b4ba2ca1a2c" }, "files": [ "config/packages/twig.yaml", "templates/base.html.twig" ] }, + "symfony/uid": { + "version": "6.2", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.2", + "ref": "d294ad4add3e15d7eb1bae0221588ca89b38e558" + }, + "files": [ + "./config/packages/uid.yaml" + ] + }, "symfony/ux-turbo": { "version": "v2.0.1" }, @@ -756,12 +756,12 @@ "version": "v4.2.3" }, "symfony/web-profiler-bundle": { - "version": "5.4", + "version": "6.3", "recipe": { "repo": "github.com/symfony/recipes", - "branch": "master", - "version": "5.3", - "ref": "24bbc3d84ef2f427f82104f766014e799eefcc3e" + "branch": "main", + "version": "6.1", + "ref": "e42b3f0177df239add25373083a564e5ead4e13a" }, "files": [ "config/packages/web_profiler.yaml", @@ -769,12 +769,12 @@ ] }, "symfony/webpack-encore-bundle": { - "version": "1.16", + "version": "1.17", "recipe": { "repo": "github.com/symfony/recipes", "branch": "main", "version": "1.10", - "ref": "f8fc53f1942f76679e9ee3c25fd44865355707b5" + "ref": "eff2e505d4557c967b6710fe06bd947ba555cae5" }, "files": [ "assets/app.js", diff --git a/templates/_navbar.html.twig b/templates/_navbar.html.twig index 14e40151..3ca68a21 100644 --- a/templates/_navbar.html.twig +++ b/templates/_navbar.html.twig @@ -1,6 +1,6 @@ {% import "helper.twig" as helper %} -