diff --git a/05_react/unterricht/tag54/react-app/src/components/tables/TableProducts.jsx b/05_react/unterricht/tag54/react-app/src/components/tables/TableProducts.jsx index ce45344..2d6468a 100644 --- a/05_react/unterricht/tag54/react-app/src/components/tables/TableProducts.jsx +++ b/05_react/unterricht/tag54/react-app/src/components/tables/TableProducts.jsx @@ -4,7 +4,19 @@ import RowProduct from './content/RowProduct'; // Reducer (bei größerer Applikaitonsstuktur ausgelagert) const initialState = (props) => { // Validierung und Error Checks - return { items: props.items || [] }; + items.filter((item) => !Object.hasOwn(item, _id)); + + return { + items: props.items || [ + { + _id: '1', + title: 'Example Product 1', + sku: 'ABC1234', + stock: 2, + price: 15.2, + }, + ], + }; }; const reducer = (state, action) => { @@ -65,9 +77,9 @@ const TableProducts = (props) => { const handleAmount = useCallback((amount, id) => { dispatch({ type: 'UPDATE_AMOUNT', payload: { amount, id } }); - // setItems((prevItems) => { - // return prevItems.map((item) => (item._id === id ? { ...item, stock: amount } : item)); - // }); + setItems((prevItems) => { + return prevItems.map((item) => (item._id === id ? { ...item, stock: amount } : item)); + }); }, []); const handlePrice = useCallback((price, id) => { diff --git a/webseite-react-php/react-app/package-lock.json b/webseite-react-php/react-app/package-lock.json index 835011f..e128cc6 100644 --- a/webseite-react-php/react-app/package-lock.json +++ b/webseite-react-php/react-app/package-lock.json @@ -14,7 +14,7 @@ "react": "^19.2.8", "react-dom": "^19.2.8", "react-icons": "^5.7.0", - "react-router-dom": "^7.18.2" + "react-router-dom": "^7.18.3" }, "devDependencies": { "@types/react": "^19.2.18", @@ -1963,9 +1963,9 @@ } }, "node_modules/react-router": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", - "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==", + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.3.tgz", + "integrity": "sha512-gyXgtdr5uACJ5b1Q4udzjVV+tb/rlHIMJKuJ0e89R4Kzgz47z/rgP0dIKxktqIEUhDHluGTPJJH/wRha7CyqsA==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -1985,12 +1985,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz", - "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==", + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.3.tgz", + "integrity": "sha512-ytVbyBBM7vMfRCam25r0WMhSVSom909A8p+8m0/f1w853dz/xfFu6etAT2SEbVoSnI+ZoPRDqIsQXVT89gp7kg==", "license": "MIT", "dependencies": { - "react-router": "7.18.2" + "react-router": "7.18.3" }, "engines": { "node": ">=20.0.0" diff --git a/webseite-react-php/react-app/package.json b/webseite-react-php/react-app/package.json index c239027..ad25101 100644 --- a/webseite-react-php/react-app/package.json +++ b/webseite-react-php/react-app/package.json @@ -16,7 +16,7 @@ "react": "^19.2.8", "react-dom": "^19.2.8", "react-icons": "^5.7.0", - "react-router-dom": "^7.18.2" + "react-router-dom": "^7.18.3" }, "devDependencies": { "@types/react": "^19.2.18", diff --git a/webseite-react-php/react-app/src/components/tables/TableProducts.jsx b/webseite-react-php/react-app/src/components/tables/TableProducts.jsx index 355bbfe..1457e0f 100644 --- a/webseite-react-php/react-app/src/components/tables/TableProducts.jsx +++ b/webseite-react-php/react-app/src/components/tables/TableProducts.jsx @@ -3,8 +3,9 @@ import RowProduct from './content/RowProduct'; import { ProductsProvider, useProductsState } from '../../stores/products'; const TableProductsView = (props) => { - const { items } = useProductsState(); - // const dispatch = useProductsDispatch() + const { items } = useProductsState(); // const {items} = useContext(ProductsStateContext); + + // const dispatch = useProductsDispatch() // const dispatch = useContext(ProductsDispatchContext); //const [{ items }, dispatch] = useReducer(reducer, initialState(props)); diff --git a/webseite-react-php/react-app/src/components/tables/content/RowProduct.jsx b/webseite-react-php/react-app/src/components/tables/content/RowProduct.jsx index 5d05fa6..6773b2e 100644 --- a/webseite-react-php/react-app/src/components/tables/content/RowProduct.jsx +++ b/webseite-react-php/react-app/src/components/tables/content/RowProduct.jsx @@ -1,7 +1,8 @@ import { memo, useCallback } from 'react'; import { useProductsActions } from '../../../stores/products'; // /index.js muss nicht angegeben werden (wg. Bundler) + // import { useProductsDispatch } from '../../../stores/products'; -// import { PRODUCTS_ACTIONS } from '../../../stores/products'; +//import { PRODUCTS_ACTIONS } from '../../../stores/products'; import InputAmount from '../../inputs/InputAmount'; import InputPrice from '../../inputs/InputPrice'; @@ -11,7 +12,7 @@ const RowProduct = (props) => { //const { sku = '', title = '' } = props; const { updateAmount, updatePrice } = useProductsActions(); - // const dispatch = useProductsDispatch(); + // const dispatch = useProductsDispatch(); // const dispatch = useContext(ProductsDispatchContext) // Eventhandler // const handleAmount = useCallback((val) => { @@ -23,6 +24,7 @@ const RowProduct = (props) => { const handleAmount = useCallback( (val) => { updateAmount(val, _id); + // dispatch({type: PRODUCTS_ACTIONS.UPDATE_AMOUNT, payload: {amount: val, id: _id}}) }, [updateAmount, _id], ); diff --git a/webseite-react-php/react-app/src/stores/products/ProductsStore.jsx b/webseite-react-php/react-app/src/stores/products/ProductsStore.jsx index 9f14457..5850318 100644 --- a/webseite-react-php/react-app/src/stores/products/ProductsStore.jsx +++ b/webseite-react-php/react-app/src/stores/products/ProductsStore.jsx @@ -44,6 +44,7 @@ export const useProductsDispatch = () => { return dispatch; }; +// Factory Function export const useProductsActions = () => { const dispatch = useProductsDispatch(); diff --git a/webseite-react-php/slim-php/.env.example b/webseite-react-php/slim-php/.env.example new file mode 100644 index 0000000..1a84663 --- /dev/null +++ b/webseite-react-php/slim-php/.env.example @@ -0,0 +1,6 @@ +DB_HOST=127.0.0.1 +DB_PORT=8889 +DB_NAME=nerdshop +DB_USER=root +DB_PASS=root +CORS_ORIGIN=http://localhost:5173 diff --git a/webseite-react-php/slim-php/.gitignore b/webseite-react-php/slim-php/.gitignore new file mode 100644 index 0000000..3d5163b --- /dev/null +++ b/webseite-react-php/slim-php/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +.env diff --git a/webseite-react-php/slim-php/INSTALLATION.md b/webseite-react-php/slim-php/INSTALLATION.md new file mode 100644 index 0000000..51fd94d --- /dev/null +++ b/webseite-react-php/slim-php/INSTALLATION.md @@ -0,0 +1,100 @@ +## MacOS Installation + +Um die PHP-Version und Composer aus MAMP standardmäßig in deinem Terminal über die `~/.bashrc` zu nutzen, fügst du den Pfad zur gewünschten PHP-Version deinem `$PATH` voran. + +**1. MAMP PHP-Version ermitteln** +Prüfe im Finder unter `/Applications/MAMP/bin/php/`, welche Versionen installiert sind (z. B. `php8.2.0`). + +**2. `.bashrc` bearbeiten** +Öffne die Datei im Editor: + +```bash +nano ~/.bashrc + +``` + +Füge am Ende folgende Zeilen ein (ersetze `php8.2.0` durch deine tatsächliche Version): + +```bash +# MAMP PHP zum PATH hinzufügen +export PATH="/Applications/MAMP/bin/php/php8.2.0/bin:$PATH" + +# Falls Composer separat als .phar oder Skript liegt, optional ein Alias setzen: +# alias composer="/Applications/MAMP/bin/php/php8.2.0/bin/php /usr/local/bin/composer" + +``` + +**3. Änderungen anwenden** +Lade die Konfiguration neu: + +```bash +source ~/.bashrc + +``` + +**4. Überprüfen** +Teste, ob die Pfade korrekt geladen werden: + +```bash +which php +php -v +composer -v + +``` + +> **Hinweis für macOS:** Standardmäßig nutzt das Terminal unter macOS seit macOS Catalina `zsh` statt `bash`. Falls deine Änderungen nach einem Terminal-Neustart nicht greifen, trage die gleichen Zeilen stattdessen in deine `~/.zshrc` ein. + + +## Windows Installation + +Unter Windows nutzt du für Bash in der Regel **Git Bash** oder **WSL**. In Git Bash entspricht `~/.bashrc` der Datei `C:\Users\\.bashrc`. + +**1. MAMP PHP-Version ermitteln** +Prüfe unter `C:\MAMP\bin\php\`, welche PHP-Version installiert ist (z. B. `php8.2.0`). + +**2. `.bashrc` in Git Bash anpassen** +Öffne die Datei in Git Bash: + +```bash +code ~/.bashrc + +```**** + +Füge folgende Zeilen ein (Pfad an deine PHP-Version anpassen): + +```bash +# MAMP PHP zum PATH hinzufügen (Windows-Pfad im POSIX-Format) +export PATH="/c/MAMP/bin/php/php8.2.0:$PATH" + +# Composer Alias (falls composer.phar im MAMP-Verzeichnis oder Benutzerverzeichnis liegt) +alias composer="/c/MAMP/bin/php/php8.2.0/php.exe /c/ProgramData/ComposerSetup/bin/composer.phar" + +``` + +> Falls Composer als Windows-Batch-Datei (`composer.bat`) installiert ist, reicht bereits der PHP-Eintrag im `PATH`, da Git Bash installierte Windows-Programme direkt aufrufen kann. + +**3. Änderungen anwenden** + +```bash +source ~/.bashrc + +``` + +**4. Überprüfen** + +```bash +which php +php -v +composer --version + +``` + +--- + +**Alternative: Systemweit für CMD / PowerShell setzen** +Falls du PHP auch in der Windows Eingabeaufforderung oder PowerShell nutzen möchtest: + +1. `Win + R` drücken, `sysdm.cpl` eingeben und Enter drücken. +2. Reiter **Erweitert** → **Umgebungsvariablen**. +3. Unter **Systemvariablen** (oder **Benutzervariablen**) die Variable `Path` auswählen und auf **Bearbeiten** klicken. +4. Neuen Eintrag hinzufügen: `C:\MAMP\bin\php\php8.2.0` und ganz nach oben schieben. \ No newline at end of file diff --git a/webseite-react-php/slim-php/README.md b/webseite-react-php/slim-php/README.md new file mode 100644 index 0000000..4cd0d80 --- /dev/null +++ b/webseite-react-php/slim-php/README.md @@ -0,0 +1,28 @@ +Die MySQL-Datenbank und die Slim-REST-API sind eingerichtet und getestet. + +**Datenbank `nerdshop`** (MAMP, Port 8889) enthält die Tabelle `products` mit den 13 Einträgen aus der JSON-Datei. Die Mongo-IDs bleiben als `id` erhalten; die API liefert sie als `_id`, damit die React-App kompatibel bleibt. + +**API läuft auf** [http://localhost:8080](http://localhost:8080) + +| Methode | URL | Status | +| -------- | ---------------- | ------------- | +| `GET` | `/products` | alle Produkte | +| `GET` | `/products/{id}` | ein Produkt | +| `POST` | `/products` | anlegen | +| `PUT` | `/products/{id}` | aktualisieren | +| `DELETE` | `/products/{id}` | löschen | + +CORS ist für `http://localhost:5173` (Vite) gesetzt. In der React-App reicht später: + +```js +fetch('http://localhost:8080/products'); +``` + +Server neu starten: + +```bash +cd slim-php-framework +php -S localhost:8080 -t public public/router.php +``` + +Falls MAMP andere Zugangsdaten nutzt, stehen sie in `slim-php-framework/.env`. diff --git a/webseite-react-php/slim-php/composer.json b/webseite-react-php/slim-php/composer.json new file mode 100644 index 0000000..f96b062 --- /dev/null +++ b/webseite-react-php/slim-php/composer.json @@ -0,0 +1,22 @@ +{ + "name": "nerdshop/slim-api", + "version": "1.0.0", + "description": "Nerdshop REST API", + "require": { + "php": "^8.1", + "ext-pdo": "*", + "ext-pdo_mysql": "*", + "php-di/php-di": "^7.0", + "slim/psr7": "^1.7", + "slim/slim": "^4.14", + "vlucas/phpdotenv": "^5.6" + }, + "autoload": { + "psr-4": { + "App\\": "src/" + } + }, + "config": { + "sort-packages": true + } +} diff --git a/webseite-react-php/slim-php/composer.lock b/webseite-react-php/slim-php/composer.lock new file mode 100644 index 0000000..a33747f --- /dev/null +++ b/webseite-react-php/slim-php/composer.lock @@ -0,0 +1,1354 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "b8704e63b45921414a48d118ce382b57", + "packages": [ + { + "name": "fig/http-message-util", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message-util.git", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message-util/zipball/9d94dc0154230ac39e5bf89398b324a86f63f765", + "reference": "9d94dc0154230ac39e5bf89398b324a86f63f765", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "suggest": { + "psr/http-message": "The package containing the PSR-7 interfaces" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Fig\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Utility classes and constants for use with PSR-7 (psr/http-message)", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-message-util/issues", + "source": "https://github.com/php-fig/http-message-util/tree/1.1.5" + }, + "time": "2020-11-24T22:02:12+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "adccca3324eece92ca35463648c12b9e6293c05b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/adccca3324eece92ca35463648c12b9e6293c05b", + "reference": "adccca3324eece92ca35463648c12b9e6293c05b", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.10" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.52 || ^9.6.34 || ^10.5.63 || ^11.5.55 || ^12.5.14" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2026-08-24T09:06:52+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.16", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", + "reference": "7cfc24e4fa2cca045fb8dd2a797a2b2b13b655ed", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2026-08-18T20:28:54+00:00" + }, + { + "name": "nikic/fast-route", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/nikic/FastRoute.git", + "reference": "7476684f39bb9124b3be69ed3d84b66723920298" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/7476684f39bb9124b3be69ed3d84b66723920298", + "reference": "7476684f39bb9124b3be69ed3d84b66723920298", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "FastRoute\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov", + "email": "nikic@php.net" + } + ], + "description": "Fast request router for PHP", + "keywords": [ + "router", + "routing" + ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/1.3.1" + }, + "time": "2026-07-09T19:38:47+00:00" + }, + { + "name": "php-di/invoker", + "version": "2.3.7", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/Invoker.git", + "reference": "3c1ddfdef181431fbc4be83378f6d036d59e81e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/3c1ddfdef181431fbc4be83378f6d036d59e81e1", + "reference": "3c1ddfdef181431fbc4be83378f6d036d59e81e1", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "psr/container": "^1.0|^2.0" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "mnapoli/hard-mode": "~0.3.0", + "phpunit/phpunit": "^9.0 || ^10 || ^11 || ^12" + }, + "type": "library", + "autoload": { + "psr-4": { + "Invoker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Generic and extensible callable invoker", + "homepage": "https://github.com/PHP-DI/Invoker", + "keywords": [ + "callable", + "dependency", + "dependency-injection", + "injection", + "invoke", + "invoker" + ], + "support": { + "issues": "https://github.com/PHP-DI/Invoker/issues", + "source": "https://github.com/PHP-DI/Invoker/tree/2.3.7" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + } + ], + "time": "2025-08-30T10:22:22+00:00" + }, + { + "name": "php-di/php-di", + "version": "7.1.1", + "source": { + "type": "git", + "url": "https://github.com/PHP-DI/PHP-DI.git", + "reference": "f88054cc052e40dbe7b383c8817c19442d480352" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/f88054cc052e40dbe7b383c8817c19442d480352", + "reference": "f88054cc052e40dbe7b383c8817c19442d480352", + "shasum": "" + }, + "require": { + "laravel/serializable-closure": "^1.0 || ^2.0", + "php": ">=8.0", + "php-di/invoker": "^2.0", + "psr/container": "^1.1 || ^2.0" + }, + "provide": { + "psr/container-implementation": "^1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3", + "friendsofphp/proxy-manager-lts": "^1", + "mnapoli/phpunit-easymock": "^1.3", + "phpunit/phpunit": "^9.6 || ^10 || ^11", + "vimeo/psalm": "^5|^6" + }, + "suggest": { + "friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "DI\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The dependency injection container for humans", + "homepage": "https://php-di.org/", + "keywords": [ + "PSR-11", + "container", + "container-interop", + "dependency injection", + "di", + "ioc", + "psr11" + ], + "support": { + "issues": "https://github.com/PHP-DI/PHP-DI/issues", + "source": "https://github.com/PHP-DI/PHP-DI/tree/7.1.1" + }, + "funding": [ + { + "url": "https://github.com/mnapoli", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/php-di/php-di", + "type": "tidelift" + } + ], + "time": "2025-08-16T11:10:48+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/67b192b6a42ec03944b972d6e633ddec78ad2c6d", + "reference": "67b192b6a42ec03944b972d6e633ddec78ad2c6d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.54 || ^9.6.36 || ^10.5.64 || ^11.5.56 || ^12.5.33" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.10.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2026-08-24T00:54:40+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/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "time": "2023-04-10T20:06:20+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" + }, + "time": "2023-04-11T06:14:47+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "slim/psr7", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim-Psr7.git", + "reference": "76e7e3b1cdfd583e9035c4c966c08e01e45ce959" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/76e7e3b1cdfd583e9035c4c966c08e01e45ce959", + "reference": "76e7e3b1cdfd583e9035c4c966c08e01e45ce959", + "shasum": "" + }, + "require": { + "fig/http-message-util": "^1.1.5", + "php": "^8.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.0 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "^1.0", + "psr/http-message-implementation": "^1.0 || ^2.0" + }, + "require-dev": { + "adriansuter/php-autoload-override": "^1.5|| ^2.0", + "ext-json": "*", + "http-interop/http-factory-tests": "^1.0 || ^2.0", + "php-http/psr7-integration-tests": "^1.5", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^9.6 || ^10", + "squizlabs/php_codesniffer": "^3.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Psr7\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "https://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "https://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "https://www.lgse.com" + } + ], + "description": "Strict PSR-7 implementation", + "homepage": "https://www.slimframework.com", + "keywords": [ + "http", + "psr-7", + "psr7" + ], + "support": { + "issues": "https://github.com/slimphp/Slim-Psr7/issues", + "source": "https://github.com/slimphp/Slim-Psr7/tree/1.8.0" + }, + "time": "2025-11-02T17:51:19+00:00" + }, + { + "name": "slim/slim", + "version": "4.15.2", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim.git", + "reference": "e12cb05ca2a14e8f459d019e87a31dc915b80470" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/e12cb05ca2a14e8f459d019e87a31dc915b80470", + "reference": "e12cb05ca2a14e8f459d019e87a31dc915b80470", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nikic/fast-route": "^1.3", + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/container": "^1.0 || ^2.0", + "psr/http-factory": "^1.1", + "psr/http-message": "^1.1 || ^2.0", + "psr/http-server-handler": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "adriansuter/php-autoload-override": "^1.4 || ^2", + "ext-simplexml": "*", + "guzzlehttp/psr7": "^2.6", + "httpsoft/http-message": "^1.1", + "httpsoft/http-server-request": "^1.1", + "laminas/laminas-diactoros": "^2.17 || ^3", + "nyholm/psr7": "^1.8", + "nyholm/psr7-server": "^1.1", + "phpspec/prophecy": "^1.19", + "phpspec/prophecy-phpunit": "^2.1", + "phpstan/phpstan": "^1 || ^2", + "phpunit/phpunit": "^9.6 || ^10 || ^11 || ^12", + "slim/http": "^1.3", + "slim/psr7": "^1.6", + "squizlabs/php_codesniffer": "^3.10", + "vimeo/psalm": "^5 || ^6" + }, + "suggest": { + "ext-simplexml": "Needed to support XML format in BodyParsingMiddleware", + "ext-xml": "Needed to support XML format in BodyParsingMiddleware", + "php-di/php-di": "PHP-DI is the recommended container library to be used with Slim", + "slim/psr7": "Slim PSR-7 implementation. See https://www.slimframework.com/docs/v4/start/installation.html for more information." + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\": "Slim" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "https://joshlockhart.com" + }, + { + "name": "Andrew Smith", + "email": "a.smith@silentworks.co.uk", + "homepage": "https://silentworks.co.uk" + }, + { + "name": "Rob Allen", + "email": "rob@akrabat.com", + "homepage": "https://akrabat.com" + }, + { + "name": "Pierre Berube", + "email": "pierre@lgse.com", + "homepage": "https://www.lgse.com" + }, + { + "name": "Gabriel Manricks", + "email": "gmanricks@me.com", + "homepage": "http://gabrielmanricks.com" + } + ], + "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", + "homepage": "https://www.slimframework.com", + "keywords": [ + "api", + "framework", + "micro", + "router" + ], + "support": { + "docs": "https://www.slimframework.com/docs/v4/", + "forum": "https://discourse.slimframework.com/", + "irc": "irc://irc.freenode.net:6667/slimphp", + "issues": "https://github.com/slimphp/Slim/issues", + "rss": "https://www.slimframework.com/blog/feed.rss", + "slack": "https://slimphp.slack.com/", + "source": "https://github.com/slimphp/Slim", + "wiki": "https://github.com/slimphp/Slim/wiki" + }, + "funding": [ + { + "url": "https://opencollective.com/slimphp", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slim/slim", + "type": "tidelift" + } + ], + "time": "2026-05-22T08:00:12+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-27T06:59:30+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "301c07936b16d88628b126b01d082ba153cf4c40" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/301c07936b16d88628b126b01d082ba153cf4c40", + "reference": "301c07936b16d88628b126b01d082ba153cf4c40", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.2", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.10", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `$_ENV` and `$_SERVER` automagically, and optionally to `getenv()`.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.7.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2026-08-24T18:07:49+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": "^8.1", + "ext-pdo": "*", + "ext-pdo_mysql": "*" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/webseite-react-php/slim-php/public/.htaccess b/webseite-react-php/slim-php/public/.htaccess new file mode 100644 index 0000000..66ef8f6 --- /dev/null +++ b/webseite-react-php/slim-php/public/.htaccess @@ -0,0 +1,4 @@ +RewriteEngine On +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^ index.php [QSA,L] diff --git a/webseite-react-php/slim-php/public/index.php b/webseite-react-php/slim-php/public/index.php new file mode 100644 index 0000000..ed3031f --- /dev/null +++ b/webseite-react-php/slim-php/public/index.php @@ -0,0 +1,33 @@ +safeLoad(); + +$container = new Container(); +$container->set(PDO::class, static fn () => Config::pdo()); +$container->set( + ProductRepository::class, + static fn ($c) => new ProductRepository($c->get(PDO::class)) +); + +AppFactory::setContainer($container); +$app = AppFactory::create(); + +$app->addBodyParsingMiddleware(); +$app->addRoutingMiddleware(); +$app->addErrorMiddleware(true, true, true); +$app->add(new CorsMiddleware()); + +(require __DIR__ . '/../src/Routes/productRoutes.php')($app); + +$app->run(); diff --git a/webseite-react-php/slim-php/public/router.php b/webseite-react-php/slim-php/public/router.php new file mode 100644 index 0000000..71c26d8 --- /dev/null +++ b/webseite-react-php/slim-php/public/router.php @@ -0,0 +1,11 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ] + ); + } +} diff --git a/webseite-react-php/slim-php/src/Domain/Product.php b/webseite-react-php/slim-php/src/Domain/Product.php new file mode 100644 index 0000000..fe7b154 --- /dev/null +++ b/webseite-react-php/slim-php/src/Domain/Product.php @@ -0,0 +1,46 @@ + $this->id, + 'title' => $this->title, + 'sku' => $this->sku, + 'stock' => $this->stock, + 'price' => $this->price, + 'description' => $this->description, + 'tagline' => $this->tagline, + ]; + } +} diff --git a/webseite-react-php/slim-php/src/Domain/User.php b/webseite-react-php/slim-php/src/Domain/User.php new file mode 100644 index 0000000..469503a --- /dev/null +++ b/webseite-react-php/slim-php/src/Domain/User.php @@ -0,0 +1,40 @@ + $this->id, + 'username' => $this->username, + 'email' => $this->email, + 'created_at' => $this->createdAt, + ]; + } +} diff --git a/webseite-react-php/slim-php/src/Middleware/CorsMiddleware.php b/webseite-react-php/slim-php/src/Middleware/CorsMiddleware.php new file mode 100644 index 0000000..ed08757 --- /dev/null +++ b/webseite-react-php/slim-php/src/Middleware/CorsMiddleware.php @@ -0,0 +1,30 @@ +getMethod() === 'OPTIONS') { + $response = new SlimResponse(204); + } else { + $response = $handler->handle($request); + } + + return $response + ->withHeader('Access-Control-Allow-Origin', $origin) + ->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization') + ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS'); + } +} diff --git a/webseite-react-php/slim-php/src/Repository/ProductRepository.php b/webseite-react-php/slim-php/src/Repository/ProductRepository.php new file mode 100644 index 0000000..6e538bd --- /dev/null +++ b/webseite-react-php/slim-php/src/Repository/ProductRepository.php @@ -0,0 +1,115 @@ +pdo->query('SELECT * FROM products ORDER BY sku'); + + return array_map(Product::fromRow(...), $stmt->fetchAll()); + } + + public function findById(string $id): ?Product + { + $stmt = $this->pdo->prepare('SELECT * FROM products WHERE id = :id'); + $stmt->execute(['id' => $id]); + $row = $stmt->fetch(); + + return $row ? Product::fromRow($row) : null; + } + + public function create(array $data): Product + { + $id = $data['_id'] ?? $this->generateId(); + + $stmt = $this->pdo->prepare( + 'INSERT INTO products (id, title, sku, stock, price, description, tagline) + VALUES (:id, :title, :sku, :stock, :price, :description, :tagline)' + ); + $stmt->execute([ + 'id' => $id, + 'title' => $data['title'], + 'sku' => $data['sku'], + 'stock' => (int) $data['stock'], + 'price' => (float) $data['price'], + 'description' => $data['description'], + 'tagline' => $data['tagline'], + ]); + + $product = $this->findById($id); + + if ($product === null) { + throw new \RuntimeException('Product could not be created'); + } + + return $product; + } + + public function update(string $id, array $data): ?Product + { + if ($this->findById($id) === null) { + return null; + } + + $stmt = $this->pdo->prepare( + 'UPDATE products + SET title = :title, sku = :sku, stock = :stock, + price = :price, description = :description, tagline = :tagline + WHERE id = :id' + ); + $stmt->execute([ + 'id' => $id, + 'title' => $data['title'], + 'sku' => $data['sku'], + 'stock' => (int) $data['stock'], + 'price' => (float) $data['price'], + 'description' => $data['description'], + 'tagline' => $data['tagline'], + ]); + + return $this->findById($id); + } + + public function patch(string $id, array $data): ?Product + { + $current = $this->findById($id); + + if ($current === null) { + return null; + } + + return $this->update($id, [ + 'title' => $data['title'] ?? $current->title, + 'sku' => $data['sku'] ?? $current->sku, + 'stock' => array_key_exists('stock', $data) ? (int) $data['stock'] : $current->stock, + 'price' => array_key_exists('price', $data) ? (float) $data['price'] : $current->price, + 'description' => $data['description'] ?? $current->description, + 'tagline' => $data['tagline'] ?? $current->tagline, + ]); + } + + public function delete(string $id): bool + { + $stmt = $this->pdo->prepare('DELETE FROM products WHERE id = :id'); + $stmt->execute(['id' => $id]); + + return $stmt->rowCount() > 0; + } + + private function generateId(): string + { + return bin2hex(random_bytes(12)); + } +} diff --git a/webseite-react-php/slim-php/src/Repository/UserRepository.php b/webseite-react-php/slim-php/src/Repository/UserRepository.php new file mode 100644 index 0000000..aac29d9 --- /dev/null +++ b/webseite-react-php/slim-php/src/Repository/UserRepository.php @@ -0,0 +1,43 @@ +pdo->query('SELECT * FROM users ORDER BY username'); + + return array_map(User::fromRow(...), $stmt->fetchAll()); + } + + public function findById(int $id): ?User + { + $stmt = $this->pdo->prepare('SELECT * FROM users WHERE id = :id'); + $stmt->execute(['id' => $id]); + $row = $stmt->fetch(); + + return $row ? User::fromRow($row) : null; + } + + public function findByUsernameOrEmail(string $login): ?User + { + $stmt = $this->pdo->prepare( + 'SELECT * FROM users WHERE username = :login OR email = :login LIMIT 1' + ); + $stmt->execute(['login' => $login]); + $row = $stmt->fetch(); + + return $row ? User::fromRow($row) : null; + } +} diff --git a/webseite-react-php/slim-php/src/Routes/productRoutes.php b/webseite-react-php/slim-php/src/Routes/productRoutes.php new file mode 100644 index 0000000..e0fed4c --- /dev/null +++ b/webseite-react-php/slim-php/src/Routes/productRoutes.php @@ -0,0 +1,155 @@ +getContainer(); + + if ($container === null) { + throw new RuntimeException('DI container is not configured'); + } + + $json = static function (Response $response, mixed $data, int $status = 200): Response { + $response->getBody()->write(json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + + return $response + ->withHeader('Content-Type', 'application/json') + ->withStatus($status); + }; + + $requiredFields = ['title', 'sku', 'stock', 'price', 'description', 'tagline']; + + $validate = static function (array $data) use ($requiredFields): ?string { + foreach ($requiredFields as $field) { + if (!isset($data[$field]) || $data[$field] === '') { + return "Missing field: {$field}"; + } + } + + return null; + }; + + $app->get('/', function (Request $request, Response $response) use ($json): Response { + + return $json($response, [ + 'name' => 'Nerdshop API', + 'endpoints' => [ + 'GET /products', + 'GET /products/{id}', + 'POST /products', + 'PUT /products/{id}', + 'PATCH /products/{id}', + 'DELETE /products/{id}', + ], + ]); + }); + + // app.get('/products', (req,res) => { + // const products = productManager.getAll(); + // res.send(products); + // }) + + $app->get('/products', function (Request $request, Response $response) use ($container, $json): Response { + $repo = $container->get(ProductRepository::class); + $items = array_map(static fn($product) => $product->toArray(), $repo->findAll()); + + return $json($response, $items); + }); + + $app->get('/products/{id}', function (Request $request, Response $response, array $args) use ($container, $json): Response { + $product = $container->get(ProductRepository::class)->findById($args['id']); + + if ($product === null) { + return $json($response, ['error' => 'Product not found'], 404); + } + + return $json($response, $product->toArray()); + }); + + $app->post('/products', function (Request $request, Response $response) use ($container, $json, $validate): Response { + $data = (array) $request->getParsedBody(); + $error = $validate($data); + + if ($error !== null) { + return $json($response, ['error' => $error], 400); + } + + try { + $product = $container->get(ProductRepository::class)->create($data); + } catch (\PDOException $exception) { + if ((int) $exception->getCode() === 23000) { + return $json($response, ['error' => 'SKU already exists'], 409); + } + + throw $exception; + } + + return $json($response, $product->toArray(), 201); + }); + + $app->put('/products/{id}', function (Request $request, Response $response, array $args) use ($container, $json, $validate): Response { + $data = (array) $request->getParsedBody(); + $error = $validate($data); + + if ($error !== null) { + return $json($response, ['error' => $error], 400); + } + + try { + $product = $container->get(ProductRepository::class)->update($args['id'], $data); + } catch (\PDOException $exception) { + if ((int) $exception->getCode() === 23000) { + return $json($response, ['error' => 'SKU already exists'], 409); + } + + throw $exception; + } + + if ($product === null) { + return $json($response, ['error' => 'Product not found'], 404); + } + + return $json($response, $product->toArray()); + }); + + $app->patch('/products/{id}', function (Request $request, Response $response, array $args) use ($container, $json): Response { + $data = (array) $request->getParsedBody(); + $allowed = ['title', 'sku', 'stock', 'price', 'description', 'tagline']; + $patch = array_intersect_key($data, array_flip($allowed)); + + if ($patch === []) { + return $json($response, ['error' => 'No fields to update'], 400); + } + + try { + $product = $container->get(ProductRepository::class)->patch($args['id'], $patch); + } catch (\PDOException $exception) { + if ((int) $exception->getCode() === 23000) { + return $json($response, ['error' => 'SKU already exists'], 409); + } + + throw $exception; + } + + if ($product === null) { + return $json($response, ['error' => 'Product not found'], 404); + } + + return $json($response, $product->toArray()); + }); + + $app->delete('/products/{id}', function (Request $request, Response $response, array $args) use ($container, $json): Response { + $deleted = $container->get(ProductRepository::class)->delete($args['id']); + + if (!$deleted) { + return $json($response, ['error' => 'Product not found'], 404); + } + + return $response->withStatus(204); + }); +};