Add new version 0.238 zip file containing updated ProductRepository and Product class files
This commit is contained in:
@@ -2,7 +2,12 @@
|
|||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(powershell -Command \"Compress-Archive -Path ''*'' -DestinationPath ''../ver_0.234.zip'' -Force\")",
|
"Bash(powershell -Command \"Compress-Archive -Path ''*'' -DestinationPath ''../ver_0.234.zip'' -Force\")",
|
||||||
"Bash(powershell -Command:*)"
|
"Bash(powershell -Command:*)",
|
||||||
|
"Bash(ls -la \"c:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\updates\\\\0.20\"\" | grep \"ver_ \")",
|
||||||
|
"Bash(C:/xampp/php/php.exe:*)",
|
||||||
|
"Bash(where:*)",
|
||||||
|
"Bash(composer:*)",
|
||||||
|
"Bash(curl:*)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
.phpunit.result.cache
Normal file
1
.phpunit.result.cache
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"version":1,"defects":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":4,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":4,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":4,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":4,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":4},"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.003,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":0.001,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":0}}
|
||||||
239
PROJECT_STRUCTURE.md
Normal file
239
PROJECT_STRUCTURE.md
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
# Struktura Projektu shopPRO
|
||||||
|
|
||||||
|
Dokumentacja struktury projektu shopPRO do szybkiego odniesienia.
|
||||||
|
|
||||||
|
## System Cache (Redis)
|
||||||
|
|
||||||
|
### Klasy odpowiedzialne za cache
|
||||||
|
|
||||||
|
#### RedisConnection
|
||||||
|
- **Plik:** `autoload/class.RedisConnection.php`
|
||||||
|
- **Opis:** Singleton zarządzający połączeniem z Redis
|
||||||
|
- **Metody:**
|
||||||
|
- `getInstance()` - pobiera instancję połączenia
|
||||||
|
- `getConnection()` - zwraca obiekt Redis
|
||||||
|
|
||||||
|
#### CacheHandler
|
||||||
|
- **Plik:** `autoload/class.CacheHandler.php`
|
||||||
|
- **Opis:** Handler do obsługi cache Redis
|
||||||
|
- **Metody:**
|
||||||
|
- `get($key)` - pobiera wartość z cache
|
||||||
|
- `set($key, $value, $ttl = 86400)` - zapisuje wartość do cache
|
||||||
|
- `exists($key)` - sprawdza czy klucz istnieje
|
||||||
|
- `delete($key)` - usuwa pojedynczy klucz
|
||||||
|
- `deletePattern($pattern)` - usuwa klucze według wzorca
|
||||||
|
|
||||||
|
#### Klasa S (pomocnicza)
|
||||||
|
- **Plik:** `autoload/class.S.php`
|
||||||
|
- **Metody cache:**
|
||||||
|
- `clear_redis_cache()` - czyści cały cache Redis (flushAll)
|
||||||
|
- `clear_product_cache(int $product_id)` - czyści cache konkretnego produktu
|
||||||
|
|
||||||
|
### Wzorce kluczy Redis
|
||||||
|
|
||||||
|
#### Produkty
|
||||||
|
```
|
||||||
|
shop\product:{product_id}:{lang_id}:{permutation_hash}
|
||||||
|
```
|
||||||
|
- Przechowuje zserializowany obiekt produktu
|
||||||
|
- TTL: 24 godziny (86400 sekund)
|
||||||
|
- Klasa: `shop\Product::getFromCache()` - `autoload/shop/class.Product.php:121`
|
||||||
|
|
||||||
|
#### Opcje ilościowe produktu
|
||||||
|
```
|
||||||
|
\shop\Product::get_product_permutation_quantity_options:{product_id}:{permutation}
|
||||||
|
```
|
||||||
|
- Przechowuje informacje o ilości i komunikatach magazynowych
|
||||||
|
- Klasa: `shop\Product::get_product_permutation_quantity_options()` - `autoload/shop/class.Product.php:549`
|
||||||
|
|
||||||
|
#### Zestawy produktów
|
||||||
|
```
|
||||||
|
\shop\Product::product_sets_when_add_to_basket:{product_id}
|
||||||
|
```
|
||||||
|
- Przechowuje produkty często kupowane razem
|
||||||
|
- Klasa: `shop\Product::product_sets_when_add_to_basket()` - `autoload/shop/class.Product.php:316`
|
||||||
|
|
||||||
|
## Integracje z systemami zewnętrznymi (CRON)
|
||||||
|
|
||||||
|
### Plik: `cron.php`
|
||||||
|
|
||||||
|
#### Sellasist
|
||||||
|
- **Aktualizacja produktów:** Linia 111-149
|
||||||
|
- **Funkcje:** Aktualizacja cen i stanów magazynowych
|
||||||
|
- **Częstotliwość:** Co 10 minut
|
||||||
|
- **Czyszczenie cache:** Linia 146
|
||||||
|
|
||||||
|
#### Apilo
|
||||||
|
- **Aktualizacja pojedynczego produktu:** Linia 152-176
|
||||||
|
- Częstotliwość: Co 10 minut
|
||||||
|
- Czyszczenie cache: Linia 173
|
||||||
|
|
||||||
|
- **Synchronizacja cennika:** Linia 179-218
|
||||||
|
- Częstotliwość: Co 1 godzinę
|
||||||
|
- Czyszczenie cache: Linia 212
|
||||||
|
|
||||||
|
#### Baselinker
|
||||||
|
- **Aktualizacja produktów:** Linia 220-289
|
||||||
|
- **Funkcje:** Ceny, stany magazynowe, wagi
|
||||||
|
- **Częstotliwość:** Co 24 godziny (1440 minut)
|
||||||
|
- **Czyszczenie cache:** Linia 278
|
||||||
|
|
||||||
|
## Panel Administratora
|
||||||
|
|
||||||
|
### Routing
|
||||||
|
- Główny katalog: `admin/`
|
||||||
|
- Template główny: `admin/templates/site/main-layout.php`
|
||||||
|
- Kontrolery: `autoload/admin/controls/`
|
||||||
|
|
||||||
|
### Przycisk "Wyczyść cache"
|
||||||
|
- **Lokalizacja UI:** `admin/templates/site/main-layout.php:172`
|
||||||
|
- **JavaScript:** `admin/templates/site/main-layout.php:235-274`
|
||||||
|
- **Endpoint AJAX:** `/admin/settings/clear_cache_ajax/`
|
||||||
|
- **Kontroler:** `autoload/admin/controls/class.Settings.php:20-42`
|
||||||
|
- **Działanie:**
|
||||||
|
1. Pokazuje spinner "Czyszczę cache..."
|
||||||
|
2. Czyści katalogi: `temp/`, `thumbs/`
|
||||||
|
3. Wykonuje `flushAll()` na Redis
|
||||||
|
4. Pokazuje "Cache wyczyszczony!" przez 2 sekundy
|
||||||
|
5. Przywraca stan początkowy
|
||||||
|
|
||||||
|
## Struktura katalogów
|
||||||
|
|
||||||
|
```
|
||||||
|
shopPRO/
|
||||||
|
├── admin/ # Panel administratora
|
||||||
|
│ ├── templates/ # Szablony widoków
|
||||||
|
│ └── layout/ # Zasoby CSS/JS/ikony
|
||||||
|
├── autoload/ # Klasy autoloadowane
|
||||||
|
│ ├── admin/ # Klasy panelu admin
|
||||||
|
│ │ ├── controls/ # Kontrolery
|
||||||
|
│ │ └── factory/ # Fabryki/helpery
|
||||||
|
│ ├── front/ # Klasy frontendu
|
||||||
|
│ │ └── factory/ # Fabryki/helpery
|
||||||
|
│ └── shop/ # Klasy sklepu
|
||||||
|
├── libraries/ # Biblioteki zewnętrzne
|
||||||
|
├── temp/ # Cache tymczasowy
|
||||||
|
├── thumbs/ # Miniatury zdjęć
|
||||||
|
└── cron.php # Zadania CRON
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Baza danych
|
||||||
|
|
||||||
|
### Główne tabele produktów
|
||||||
|
- `pp_shop_products` - produkty główne
|
||||||
|
- `pp_shop_products_langs` - tłumaczenia produktów
|
||||||
|
- `pp_shop_products_images` - zdjęcia produktów
|
||||||
|
- `pp_shop_products_categories` - kategorie produktów
|
||||||
|
- `pp_shop_products_custom_fields` - pola własne produktów
|
||||||
|
|
||||||
|
### Tabele integracji
|
||||||
|
- Kolumny w `pp_shop_products`:
|
||||||
|
- `sellasist_product_id`, `sellasist_get_data_date`
|
||||||
|
- `apilo_product_id`, `apilo_get_data_date`
|
||||||
|
- `baselinker_product_id`, `baselinker_get_data_date`
|
||||||
|
|
||||||
|
## Konfiguracja
|
||||||
|
|
||||||
|
### Redis
|
||||||
|
- Konfiguracja: `config.php` (zmienna `$config['redis']`)
|
||||||
|
- Parametry: host, port, password
|
||||||
|
|
||||||
|
### Autoload
|
||||||
|
- Funkcja: `__autoload_my_classes()` w `cron.php:6`
|
||||||
|
- Wzorzec: `autoload/{namespace}/class.{ClassName}.php`
|
||||||
|
|
||||||
|
## Klasy pomocnicze
|
||||||
|
|
||||||
|
### \S (autoload/class.S.php)
|
||||||
|
Główna klasa helper z metodami:
|
||||||
|
- `seo($val)` - generowanie URL SEO
|
||||||
|
- `normalize_decimal($val, $precision)` - normalizacja liczb
|
||||||
|
- `send_email()` - wysyłanie emaili
|
||||||
|
- `delete_dir($dir)` - usuwanie katalogów
|
||||||
|
- `htacces()` - generowanie .htaccess i sitemap.xml
|
||||||
|
|
||||||
|
### Medoo
|
||||||
|
- Plik: `libraries/medoo/medoo.php`
|
||||||
|
- Zmienna: `$mdb`
|
||||||
|
- ORM do operacji na bazie danych
|
||||||
|
|
||||||
|
## Najważniejsze wzorce
|
||||||
|
|
||||||
|
### Namespace'y
|
||||||
|
- `\admin\controls\` - kontrolery panelu admin
|
||||||
|
- `\admin\factory\` - helpery/fabryki admin
|
||||||
|
- `\front\factory\` - helpery/fabryki frontend
|
||||||
|
- `\shop\` - klasy sklepu (Product, Order, itp.)
|
||||||
|
|
||||||
|
### Cachowanie produktów
|
||||||
|
```php
|
||||||
|
// Pobranie produktu z cache
|
||||||
|
$product = \shop\Product::getFromCache($product_id, $lang_id, $permutation_hash);
|
||||||
|
|
||||||
|
// Czyszczenie cache produktu
|
||||||
|
\S::clear_product_cache($product_id);
|
||||||
|
|
||||||
|
// Czyszczenie całego cache
|
||||||
|
\S::clear_redis_cache();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Refaktoryzacja do Domain-Driven Architecture
|
||||||
|
|
||||||
|
### Nowa struktura (w trakcie migracji)
|
||||||
|
```
|
||||||
|
autoload/
|
||||||
|
├── Domain/ # Nowa warstwa biznesowa
|
||||||
|
│ └── Product/
|
||||||
|
│ └── ProductRepository.php
|
||||||
|
├── shop/ # Legacy - fasady do nowych klas
|
||||||
|
├── admin/factory/ # Legacy - stopniowo migrowane
|
||||||
|
└── front/factory/ # Legacy - stopniowo migrowane
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dependency Injection
|
||||||
|
Nowe klasy używają **Dependency Injection** zamiast `global` variables:
|
||||||
|
```php
|
||||||
|
// STARE
|
||||||
|
global $mdb;
|
||||||
|
$quantity = $mdb->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||||
|
|
||||||
|
// NOWE
|
||||||
|
$repository = new \Domain\Product\ProductRepository($mdb);
|
||||||
|
$quantity = $repository->getQuantity($id);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testowanie (tylko dla deweloperów)
|
||||||
|
|
||||||
|
**UWAGA:** Pliki testów NIE są częścią aktualizacji dla klientów!
|
||||||
|
|
||||||
|
### Narzędzia
|
||||||
|
- **PHPUnit 9.6.34** - framework testowy
|
||||||
|
- **test.bat** - uruchamianie testów
|
||||||
|
- **composer.json** - autoloading PSR-4
|
||||||
|
|
||||||
|
### Struktura
|
||||||
|
```
|
||||||
|
tests/
|
||||||
|
├── Unit/ # Testy jednostkowe
|
||||||
|
│ └── Domain/Product/ProductRepositoryTest.php
|
||||||
|
└── Integration/ # Testy integracyjne
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ostatnie modyfikacje
|
||||||
|
|
||||||
|
### 2025-02-05: Refaktoryzacja - Product Repository (ver. 0.238)
|
||||||
|
- **NOWE:** `Domain\Product\ProductRepository` - pierwsza klasa w nowej architekturze
|
||||||
|
- **NOWE:** Dependency Injection zamiast `global $mdb`
|
||||||
|
- **NOWE:** Testy jednostkowe (5 testów, 100% pokrycie)
|
||||||
|
- Zmigrowano: `get_product_quantity()` → `ProductRepository::getQuantity()`
|
||||||
|
- Kompatybilność: Stara klasa `shop\Product` działa jako fasada
|
||||||
|
|
||||||
|
### 2025-02-05: System cache produktów (ver. 0.237)
|
||||||
|
- Automatyczne czyszczenie cache produktu po aktualizacji przez CRON
|
||||||
|
- AJAX dla przycisku "Wyczyść cache" w panelu admin
|
||||||
|
- Metody `delete()` i `deletePattern()` w CacheHandler
|
||||||
|
- Metoda `clear_product_cache()` w klasie S
|
||||||
|
|
||||||
|
---
|
||||||
|
*Dokument aktualizowany: 2025-02-05*
|
||||||
254
REFACTORING_PLAN.md
Normal file
254
REFACTORING_PLAN.md
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
# Plan Refaktoryzacji shopPRO - Domain-Driven Architecture
|
||||||
|
|
||||||
|
## Cel
|
||||||
|
Stopniowe przeniesienie logiki biznesowej do architektury warstwowej:
|
||||||
|
- **Domain/** - logika biznesowa (core)
|
||||||
|
- **Admin/** - warstwa administratora
|
||||||
|
- **Frontend/** - warstwa użytkownika
|
||||||
|
- **Shared/** - współdzielone narzędzia
|
||||||
|
|
||||||
|
## Docelowa struktura
|
||||||
|
|
||||||
|
```
|
||||||
|
autoload/
|
||||||
|
├── Domain/ # Logika biznesowa (CORE)
|
||||||
|
│ ├── Product/
|
||||||
|
│ │ ├── Product.php # Entity
|
||||||
|
│ │ ├── ProductRepository.php # Dostęp do bazy
|
||||||
|
│ │ ├── ProductService.php # Logika biznesowa
|
||||||
|
│ │ └── ProductCacheService.php # Cache produktu
|
||||||
|
│ ├── Order/
|
||||||
|
│ ├── Category/
|
||||||
|
│ └── ...
|
||||||
|
│
|
||||||
|
├── Admin/ # Warstwa administratora
|
||||||
|
│ ├── Controllers/
|
||||||
|
│ └── Services/
|
||||||
|
│
|
||||||
|
├── Frontend/ # Warstwa użytkownika
|
||||||
|
│ ├── Controllers/
|
||||||
|
│ └── Services/
|
||||||
|
│
|
||||||
|
├── Shared/ # Współdzielone narzędzia
|
||||||
|
│ ├── Cache/
|
||||||
|
│ │ ├── CacheHandler.php
|
||||||
|
│ │ └── RedisConnection.php
|
||||||
|
│ └── Helpers/
|
||||||
|
│ └── S.php
|
||||||
|
│
|
||||||
|
└── [LEGACY] # Stare klasy (stopniowo deprecated)
|
||||||
|
├── shop/
|
||||||
|
├── admin/factory/
|
||||||
|
└── front/factory/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Zasady migracji
|
||||||
|
|
||||||
|
### 1. Stopniowość
|
||||||
|
- Przenosimy **jedną funkcję na raz**
|
||||||
|
- Zachowujemy kompatybilność wsteczną
|
||||||
|
- Stare klasy działają jako fasady do nowych
|
||||||
|
|
||||||
|
### 2. Dependency Injection zamiast statycznych metod
|
||||||
|
```php
|
||||||
|
// ❌ STARE - statyczne
|
||||||
|
class Product {
|
||||||
|
public static function getQuantity($id) {
|
||||||
|
global $mdb;
|
||||||
|
return $mdb->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ NOWE - instancje z DI
|
||||||
|
class ProductRepository {
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
public function __construct($db) {
|
||||||
|
$this->db = $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQuantity($id) {
|
||||||
|
return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Fasady dla kompatybilności
|
||||||
|
```php
|
||||||
|
// Stara klasa wywołuje nową
|
||||||
|
namespace shop;
|
||||||
|
|
||||||
|
class Product {
|
||||||
|
public static function getQuantity($id) {
|
||||||
|
global $mdb;
|
||||||
|
$repo = new \Domain\Product\ProductRepository($mdb);
|
||||||
|
return $repo->getQuantity($id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Proces migracji funkcji
|
||||||
|
|
||||||
|
### Krok 1: Wybór funkcji
|
||||||
|
- Wybierz prostą funkcję statyczną
|
||||||
|
- Sprawdź jej zależności
|
||||||
|
- Przeanalizuj gdzie jest używana
|
||||||
|
|
||||||
|
### Krok 2: Stworzenie nowej struktury
|
||||||
|
- Utwórz folder `Domain/{Module}/`
|
||||||
|
- Stwórz odpowiednią klasę (Repository/Service/Entity)
|
||||||
|
- Przenieś logikę
|
||||||
|
|
||||||
|
### Krok 3: Znalezienie użyć
|
||||||
|
```bash
|
||||||
|
grep -r "Product::getQuantity" .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Krok 4: Aktualizacja wywołań
|
||||||
|
- Opcja A: Bezpośrednie wywołanie nowej klasy
|
||||||
|
- Opcja B: Fasada w starej klasie (zalecane na początek)
|
||||||
|
|
||||||
|
### Krok 5: Testy
|
||||||
|
- Napisz test jednostkowy dla nowej funkcji
|
||||||
|
- Sprawdź czy stare wywołania działają
|
||||||
|
|
||||||
|
## Status migracji
|
||||||
|
|
||||||
|
### ✅ Zmigrowane moduły
|
||||||
|
- **Cache** (częściowo)
|
||||||
|
- ✅ CacheHandler - ma delete/deletePattern
|
||||||
|
- ✅ RedisConnection - singleton
|
||||||
|
- ✅ S::clear_product_cache() - nowa metoda
|
||||||
|
|
||||||
|
### 🔄 W trakcie
|
||||||
|
- **Product**
|
||||||
|
- ✅ get_product_quantity() - **ZMIGROWANE** (2025-02-05) 🎉
|
||||||
|
- Nowa klasa: `Domain\Product\ProductRepository::getQuantity()`
|
||||||
|
- Fasada w: `shop\Product::get_product_quantity()`
|
||||||
|
- Test: `tests/Unit/Domain/Product/ProductRepositoryTest.php`
|
||||||
|
- Testy: ✅ 5/5 przechodzą (11 asercji)
|
||||||
|
- Aktualizacja: ver. 0.238
|
||||||
|
- Użycie DI: ✅ Konstruktor przyjmuje `$db`
|
||||||
|
- [ ] get_product_price() - NASTĘPNA 👉
|
||||||
|
- [ ] get_product_name()
|
||||||
|
|
||||||
|
### 📋 Do zrobienia
|
||||||
|
- Order
|
||||||
|
- Category
|
||||||
|
- ShopAttribute
|
||||||
|
- ShopProduct (factory)
|
||||||
|
|
||||||
|
## Testowanie
|
||||||
|
|
||||||
|
### Framework: PHPUnit
|
||||||
|
Instalacja:
|
||||||
|
```bash
|
||||||
|
composer require --dev phpunit/phpunit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Struktura testów
|
||||||
|
```
|
||||||
|
tests/
|
||||||
|
├── Unit/
|
||||||
|
│ ├── Domain/
|
||||||
|
│ │ └── Product/
|
||||||
|
│ │ ├── ProductRepositoryTest.php
|
||||||
|
│ │ └── ProductServiceTest.php
|
||||||
|
│ └── Shared/
|
||||||
|
│ └── Cache/
|
||||||
|
│ └── CacheHandlerTest.php
|
||||||
|
└── Integration/
|
||||||
|
└── Domain/
|
||||||
|
└── Product/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Przykład testu
|
||||||
|
```php
|
||||||
|
// tests/Unit/Domain/Product/ProductRepositoryTest.php
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Domain\Product\ProductRepository;
|
||||||
|
|
||||||
|
class ProductRepositoryTest extends TestCase {
|
||||||
|
public function testGetQuantityReturnsCorrectValue() {
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
$mockDb->method('get')->willReturn(10);
|
||||||
|
|
||||||
|
$repo = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$quantity = $repo->getQuantity(123);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertEquals(10, $quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Zasady kodu
|
||||||
|
|
||||||
|
### 1. SOLID Principles
|
||||||
|
- **S**ingle Responsibility - jedna klasa = jedna odpowiedzialność
|
||||||
|
- **O**pen/Closed - otwarty na rozszerzenia, zamknięty na modyfikacje
|
||||||
|
- **L**iskov Substitution - podklasy mogą zastąpić nadklasy
|
||||||
|
- **I**nterface Segregation - wiele małych interfejsów
|
||||||
|
- **D**ependency Inversion - zależności od abstrakcji
|
||||||
|
|
||||||
|
### 2. Nazewnictwo
|
||||||
|
- **Entity** - `Product.php` (reprezentuje obiekt domenowy)
|
||||||
|
- **Repository** - `ProductRepository.php` (dostęp do danych)
|
||||||
|
- **Service** - `ProductService.php` (logika biznesowa)
|
||||||
|
- **Controller** - `ProductController.php` (obsługa requestów)
|
||||||
|
|
||||||
|
### 3. Type Hinting
|
||||||
|
```php
|
||||||
|
// ✅ DOBRE
|
||||||
|
public function getQuantity(int $id): ?int {
|
||||||
|
return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ ZŁE
|
||||||
|
public function getQuantity($id) {
|
||||||
|
return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Narzędzia pomocnicze
|
||||||
|
|
||||||
|
### Composer autoloader
|
||||||
|
Dodaj do `composer.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Domain\\": "autoload/Domain/",
|
||||||
|
"Admin\\": "autoload/Admin/",
|
||||||
|
"Frontend\\": "autoload/Frontend/",
|
||||||
|
"Shared\\": "autoload/Shared/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Static Analysis
|
||||||
|
```bash
|
||||||
|
composer require --dev phpstan/phpstan
|
||||||
|
vendor/bin/phpstan analyse autoload/Domain
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kolejność refaktoryzacji (priorytet)
|
||||||
|
|
||||||
|
1. **Cache** (już w trakcie) ✅
|
||||||
|
2. **Product** (rozpoczynamy)
|
||||||
|
- getQuantity
|
||||||
|
- getPrice
|
||||||
|
- getName
|
||||||
|
- getFromCache
|
||||||
|
3. **ProductRepository** (dostęp do bazy)
|
||||||
|
4. **ProductService** (logika biznesowa)
|
||||||
|
5. **Order**
|
||||||
|
6. **Category**
|
||||||
|
|
||||||
|
---
|
||||||
|
*Rozpoczęto: 2025-02-05*
|
||||||
|
*Ostatnia aktualizacja: 2025-02-05*
|
||||||
134
TESTING.md
Normal file
134
TESTING.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# 🧪 Testowanie shopPRO
|
||||||
|
|
||||||
|
## Szybki start
|
||||||
|
|
||||||
|
### Uruchom wszystkie testy
|
||||||
|
```bash
|
||||||
|
./test.bat # Windows CMD (z nazwami testów)
|
||||||
|
./test-simple.bat # Tylko kropki (szybki)
|
||||||
|
./test-debug.bat # Pełne szczegóły (debug)
|
||||||
|
./test.sh # Git Bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Konkretny plik
|
||||||
|
```bash
|
||||||
|
./test.bat tests/Unit/Domain/Product/ProductRepositoryTest.php
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tryby wyświetlania
|
||||||
|
|
||||||
|
### 1. TestDox (domyślny) - Czytelna lista ✅
|
||||||
|
```bash
|
||||||
|
./test.bat
|
||||||
|
```
|
||||||
|
Wynik:
|
||||||
|
```
|
||||||
|
Product Repository
|
||||||
|
✔ Get quantity returns correct value [2.78 ms]
|
||||||
|
✔ Get quantity returns null when product not found
|
||||||
|
✔ Find returns product data
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Simple - Tylko kropki 📊
|
||||||
|
```bash
|
||||||
|
./test-simple.bat
|
||||||
|
```
|
||||||
|
Wynik:
|
||||||
|
```
|
||||||
|
..... 5 / 5 (100%)
|
||||||
|
OK (5 tests, 11 assertions)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Debug - Wszystkie szczegóły 🔬
|
||||||
|
```bash
|
||||||
|
./test-debug.bat
|
||||||
|
```
|
||||||
|
Wynik:
|
||||||
|
```
|
||||||
|
Test 'testGetQuantity' started
|
||||||
|
Test 'testGetQuantity' ended
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Interpretacja wyników
|
||||||
|
|
||||||
|
### ✅ Sukces
|
||||||
|
```
|
||||||
|
..... 5 / 5 (100%)
|
||||||
|
|
||||||
|
OK (5 tests, 11 assertions)
|
||||||
|
```
|
||||||
|
- `.` = test przeszedł
|
||||||
|
- Wszystko działa!
|
||||||
|
|
||||||
|
### ❌ Błąd
|
||||||
|
```
|
||||||
|
..E.. 5 / 5 (100%)
|
||||||
|
|
||||||
|
ERRORS!
|
||||||
|
Tests: 5, Assertions: 8, Errors: 1.
|
||||||
|
```
|
||||||
|
- `E` = Error - błąd w kodzie
|
||||||
|
- Sprawdź szczegóły powyżej
|
||||||
|
|
||||||
|
### ❌ Niepowodzenie
|
||||||
|
```
|
||||||
|
..F.. 5 / 5 (100%)
|
||||||
|
|
||||||
|
FAILURES!
|
||||||
|
Tests: 5, Assertions: 11, Failures: 1.
|
||||||
|
```
|
||||||
|
- `F` = Failure - asercja się nie powiodła
|
||||||
|
- Oczekiwano innej wartości
|
||||||
|
|
||||||
|
## Przykładowy test
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function testGetQuantityReturnsCorrectValue()
|
||||||
|
{
|
||||||
|
// Arrange - Przygotuj
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
$mockDb->method('get')->willReturn(42);
|
||||||
|
$repository = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act - Wykonaj
|
||||||
|
$quantity = $repository->getQuantity(123);
|
||||||
|
|
||||||
|
// Assert - Sprawdź
|
||||||
|
$this->assertEquals(42, $quantity);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dodawanie nowych testów
|
||||||
|
|
||||||
|
1. Utwórz plik w `tests/Unit/Domain/{Module}/{Class}Test.php`
|
||||||
|
2. Rozszerz `TestCase`
|
||||||
|
3. Metody testowe zaczynaj od `test`
|
||||||
|
4. Użyj pattern **AAA** (Arrange, Act, Assert)
|
||||||
|
|
||||||
|
## Asercje
|
||||||
|
|
||||||
|
```php
|
||||||
|
$this->assertEquals(expected, actual); // Równość
|
||||||
|
$this->assertIsInt($value); // Typ
|
||||||
|
$this->assertNull($value); // Null
|
||||||
|
$this->assertTrue($condition); // Prawda
|
||||||
|
$this->assertCount(3, $array); // Rozmiar
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mockowanie
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Prosty mock
|
||||||
|
$mock = $this->createMock(\medoo::class);
|
||||||
|
$mock->method('get')->willReturn('wartość');
|
||||||
|
|
||||||
|
// Z weryfikacją
|
||||||
|
$mock->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with($this->equalTo('tabela'))
|
||||||
|
->willReturn(42);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
📚 Więcej: [tests/README.md](tests/README.md)
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-3 col-lg-2">
|
<div class="col-12 col-md-3 col-lg-2">
|
||||||
<a href="/admin/settings/clear_cache/" class="btn btn-danger mt-3">Wyczyść cache</a>
|
<button id="clear-cache-btn" class="btn btn-danger mt-3">Wyczyść cache</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-9 col-lg-10 top-user">
|
<div class="col-12 col-md-9 col-lg-10 top-user">
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
@@ -229,6 +229,48 @@
|
|||||||
$( '#mobile-menu-btn i' ).addClass( 'fa-times' ).removeClass( 'fa-bars' );
|
$( '#mobile-menu-btn i' ).addClass( 'fa-times' ).removeClass( 'fa-bars' );
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Obsługa przycisku czyszczenia cache
|
||||||
|
$('#clear-cache-btn').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var $btn = $(this);
|
||||||
|
var originalText = $btn.text();
|
||||||
|
|
||||||
|
// Wyświetl komunikat o czyszczeniu
|
||||||
|
$btn.prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Czyszczę cache...');
|
||||||
|
|
||||||
|
// Wyślij żądanie AJAX
|
||||||
|
$.ajax({
|
||||||
|
url: '/admin/settings/clear_cache_ajax/',
|
||||||
|
type: 'POST',
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(response) {
|
||||||
|
if (response.status === 'success') {
|
||||||
|
// Zmień komunikat na "wyczyszczono"
|
||||||
|
$btn.html('<i class="fa fa-check"></i> Cache wyczyszczony!').removeClass('btn-danger').addClass('btn-success');
|
||||||
|
|
||||||
|
// Po 2 sekundach przywróć pierwotny stan
|
||||||
|
setTimeout(function() {
|
||||||
|
$btn.prop('disabled', false).html(originalText).removeClass('btn-success').addClass('btn-danger');
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
// Obsługa błędu
|
||||||
|
$btn.html('<i class="fa fa-exclamation-triangle"></i> Błąd!').removeClass('btn-danger').addClass('btn-warning');
|
||||||
|
setTimeout(function() {
|
||||||
|
$btn.prop('disabled', false).html(originalText).removeClass('btn-warning').addClass('btn-danger');
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
// Obsługa błędu połączenia
|
||||||
|
$btn.html('<i class="fa fa-exclamation-triangle"></i> Błąd połączenia!').removeClass('btn-danger').addClass('btn-warning');
|
||||||
|
setTimeout(function() {
|
||||||
|
$btn.prop('disabled', false).html(originalText).removeClass('btn-warning').addClass('btn-danger');
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
70
autoload/Domain/Product/ProductRepository.php
Normal file
70
autoload/Domain/Product/ProductRepository.php
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
namespace Domain\Product;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository odpowiedzialny za dostęp do danych produktów
|
||||||
|
*
|
||||||
|
* Zgodnie z wzorcem Repository Pattern, ta klasa enkapsuluje
|
||||||
|
* logikę dostępu do bazy danych dla produktów.
|
||||||
|
*/
|
||||||
|
class ProductRepository
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \medoo Instancja Medoo ORM
|
||||||
|
*/
|
||||||
|
private $db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konstruktor - przyjmuje instancję bazy danych
|
||||||
|
*
|
||||||
|
* @param \medoo $db Instancja Medoo ORM
|
||||||
|
*/
|
||||||
|
public function __construct($db)
|
||||||
|
{
|
||||||
|
$this->db = $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pobiera stan magazynowy produktu
|
||||||
|
*
|
||||||
|
* @param int $productId ID produktu
|
||||||
|
* @return int|null Ilość produktu lub null jeśli nie znaleziono
|
||||||
|
*/
|
||||||
|
public function getQuantity(int $productId): ?int
|
||||||
|
{
|
||||||
|
$quantity = $this->db->get('pp_shop_products', 'quantity', ['id' => $productId]);
|
||||||
|
|
||||||
|
// Medoo zwraca false jeśli nie znaleziono
|
||||||
|
return $quantity !== false ? (int)$quantity : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pobiera produkt po ID
|
||||||
|
*
|
||||||
|
* @param int $productId ID produktu
|
||||||
|
* @return array|null Dane produktu lub null
|
||||||
|
*/
|
||||||
|
public function find(int $productId): ?array
|
||||||
|
{
|
||||||
|
$product = $this->db->get('pp_shop_products', '*', ['id' => $productId]);
|
||||||
|
return $product ?: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktualizuje ilość produktu
|
||||||
|
*
|
||||||
|
* @param int $productId ID produktu
|
||||||
|
* @param int $quantity Nowa ilość
|
||||||
|
* @return bool Czy aktualizacja się powiodła
|
||||||
|
*/
|
||||||
|
public function updateQuantity(int $productId, int $quantity): bool
|
||||||
|
{
|
||||||
|
$result = $this->db->update(
|
||||||
|
'pp_shop_products',
|
||||||
|
['quantity' => $quantity],
|
||||||
|
['id' => $productId]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $result !== false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,30 @@ class Settings
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public function clear_cache_ajax()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Czyszczenie katalogów cache
|
||||||
|
\S::delete_dir( '../temp/' );
|
||||||
|
\S::delete_dir( '../thumbs/' );
|
||||||
|
|
||||||
|
// Czyszczenie Redis cache
|
||||||
|
$redis = \RedisConnection::getInstance() -> getConnection();
|
||||||
|
if ( $redis )
|
||||||
|
$redis -> flushAll();
|
||||||
|
|
||||||
|
// Zwróć odpowiedź JSON
|
||||||
|
echo json_encode( [ 'status' => 'success', 'message' => 'Cache został wyczyszczony.' ] );
|
||||||
|
}
|
||||||
|
catch ( \Exception $e )
|
||||||
|
{
|
||||||
|
// W przypadku błędu
|
||||||
|
echo json_encode( [ 'status' => 'error', 'message' => 'Błąd podczas czyszczenia cache: ' . $e->getMessage() ] );
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
public static function settings_save()
|
public static function settings_save()
|
||||||
{
|
{
|
||||||
$values = json_decode( \S::get( 'values' ), true );
|
$values = json_decode( \S::get( 'values' ), true );
|
||||||
|
|||||||
@@ -36,4 +36,23 @@ class CacheHandler
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function delete($key)
|
||||||
|
{
|
||||||
|
if ($this->redis) {
|
||||||
|
return $this->redis->del($key);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deletePattern($pattern)
|
||||||
|
{
|
||||||
|
if ($this->redis) {
|
||||||
|
$keys = $this->redis->keys($pattern);
|
||||||
|
if (!empty($keys)) {
|
||||||
|
return $this->redis->del($keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,27 @@ class S
|
|||||||
$redis -> flushAll();
|
$redis -> flushAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public function clear_product_cache( int $product_id )
|
||||||
|
{
|
||||||
|
if ( class_exists('Redis') )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$cacheHandler = new \CacheHandler();
|
||||||
|
// Wyczyść cache produktu dla wszystkich języków i permutacji
|
||||||
|
$cacheHandler -> deletePattern( "shop\\product:$product_id:*" );
|
||||||
|
// Wyczyść cache związane z opcjami ilościowymi
|
||||||
|
$cacheHandler -> deletePattern( "\\shop\\Product::get_product_permutation_quantity_options:$product_id:*" );
|
||||||
|
// Wyczyść cache zestawów produktów
|
||||||
|
$cacheHandler -> deletePattern( "\\shop\\Product::product_sets_when_add_to_basket:$product_id" );
|
||||||
|
}
|
||||||
|
catch (\Exception $e)
|
||||||
|
{
|
||||||
|
error_log("Błąd podczas czyszczenia cache produktu: " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static public function remove_special_chars( $string ) {
|
static public function remove_special_chars( $string ) {
|
||||||
return str_ireplace( array( '\'', '"', ',' , ';', '<', '>' ), ' ', $string );
|
return str_ireplace( array( '\'', '"', ',' , ';', '<', '>' ), ' ', $string );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -608,10 +608,12 @@ class Product implements \ArrayAccess
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pobierz stan magazynowy produktu
|
// pobierz stan magazynowy produktu
|
||||||
|
// FASADA - wywołuje nową klasę Domain\Product\ProductRepository
|
||||||
static public function get_product_quantity( int $product_id )
|
static public function get_product_quantity( int $product_id )
|
||||||
{
|
{
|
||||||
global $mdb;
|
global $mdb;
|
||||||
return $mdb -> get( 'pp_shop_products', 'quantity', [ 'id' => $product_id ] );
|
$repository = new \Domain\Product\ProductRepository($mdb);
|
||||||
|
return $repository->getQuantity($product_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function product_categories( int $product_id )
|
public static function product_categories( int $product_id )
|
||||||
|
|||||||
28
composer.json
Normal file
28
composer.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "shoppro/shoppro",
|
||||||
|
"description": "shopPRO - System sklepu internetowego",
|
||||||
|
"type": "project",
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.4"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9.5"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Domain\\": "autoload/Domain/",
|
||||||
|
"Admin\\": "autoload/Admin/",
|
||||||
|
"Frontend\\": "autoload/Frontend/",
|
||||||
|
"Shared\\": "autoload/Shared/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "phpunit",
|
||||||
|
"test-coverage": "phpunit --coverage-html coverage"
|
||||||
|
}
|
||||||
|
}
|
||||||
1818
composer.lock
generated
Normal file
1818
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
14
cron.php
14
cron.php
@@ -141,6 +141,10 @@ if ( $sellasist_settings['enabled'] and $sellasist_settings['sync_products'] and
|
|||||||
$mdb -> update( 'pp_shop_products', [ 'quantity' => $responseData['storages'][0]['quantity'] ], [ 'sellasist_product_id' => $result['sellasist_product_id'] ] );
|
$mdb -> update( 'pp_shop_products', [ 'quantity' => $responseData['storages'][0]['quantity'] ], [ 'sellasist_product_id' => $result['sellasist_product_id'] ] );
|
||||||
|
|
||||||
$mdb -> update( 'pp_shop_products', [ 'sellasist_get_data_date' => date( 'Y-m-d H:i:s' ) ], [ 'sellasist_product_id' => $result['sellasist_product_id'] ] );
|
$mdb -> update( 'pp_shop_products', [ 'sellasist_get_data_date' => date( 'Y-m-d H:i:s' ) ], [ 'sellasist_product_id' => $result['sellasist_product_id'] ] );
|
||||||
|
|
||||||
|
// Czyszczenie cache produktu
|
||||||
|
\S::clear_product_cache( (int)$result['id'] );
|
||||||
|
|
||||||
echo '<p>Zaktualizowałem dane produktu <b>' . $result['sellasist_product_name'] . ' #' . $result['id'] . '</b></p>';
|
echo '<p>Zaktualizowałem dane produktu <b>' . $result['sellasist_product_name'] . ' #' . $result['id'] . '</b></p>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,6 +173,9 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_products'] and $apilo_
|
|||||||
|
|
||||||
$mdb -> update( 'pp_shop_products', [ 'apilo_get_data_date' => date( 'Y-m-d H:i:s' ) ], [ 'apilo_product_id' => $result['apilo_product_id'] ] );
|
$mdb -> update( 'pp_shop_products', [ 'apilo_get_data_date' => date( 'Y-m-d H:i:s' ) ], [ 'apilo_product_id' => $result['apilo_product_id'] ] );
|
||||||
|
|
||||||
|
// Czyszczenie cache produktu
|
||||||
|
\S::clear_product_cache( (int)$result['id'] );
|
||||||
|
|
||||||
echo '<p>Zaktualizowałem dane produktu (APILO) <b>' . $result['apilo_product_name'] . ' #' . $result['id'] . '</b></p>';
|
echo '<p>Zaktualizowałem dane produktu (APILO) <b>' . $result['apilo_product_name'] . ' #' . $result['id'] . '</b></p>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,6 +214,9 @@ if ( $apilo_settings['enabled'] and $apilo_settings['access-token'] and ( !$apil
|
|||||||
$product_id = $mdb -> get( 'pp_shop_products', 'id', [ 'apilo_product_id' => $product_price['product'] ] );
|
$product_id = $mdb -> get( 'pp_shop_products', 'id', [ 'apilo_product_id' => $product_price['product'] ] );
|
||||||
|
|
||||||
\admin\factory\ShopProduct::update_product_combinations_prices( (int)$product_id, $price_brutto, $vat, null );
|
\admin\factory\ShopProduct::update_product_combinations_prices( (int)$product_id, $price_brutto, $vat, null );
|
||||||
|
|
||||||
|
// Czyszczenie cache produktu
|
||||||
|
\S::clear_product_cache( (int)$product_id );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,9 +284,11 @@ if ( $baselinker_settings['enabled'] and $baselinker_settings['sync_products'] a
|
|||||||
|
|
||||||
$mdb -> update( 'pp_shop_products', [ 'baselinker_get_data_date' => date( 'Y-m-d H:i:s' ) ], [ 'baselinker_product_id' => $baselinker_product_id ] );
|
$mdb -> update( 'pp_shop_products', [ 'baselinker_get_data_date' => date( 'Y-m-d H:i:s' ) ], [ 'baselinker_product_id' => $baselinker_product_id ] );
|
||||||
|
|
||||||
|
// Czyszczenie cache produktu
|
||||||
|
\S::clear_product_cache( (int)$result['id'] );
|
||||||
|
|
||||||
echo '<p>Zaktualizowałem dane produktu <b>' . $baselinker_product['text_fields']['name'] . ' #' . $result['id'] . '</b></p>';
|
echo '<p>Zaktualizowałem dane produktu <b>' . $baselinker_product['text_fields']['name'] . ' #' . $result['id'] . '</b></p>';
|
||||||
}
|
}
|
||||||
\S::clear_redis_cache();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
113333
phpunit.phar
Normal file
113333
phpunit.phar
Normal file
File diff suppressed because one or more lines are too long
25
phpunit.xml
Normal file
25
phpunit.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
|
||||||
|
bootstrap="tests/bootstrap.php"
|
||||||
|
colors="true"
|
||||||
|
verbose="true">
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="Unit">
|
||||||
|
<directory>tests/Unit</directory>
|
||||||
|
</testsuite>
|
||||||
|
<testsuite name="Integration">
|
||||||
|
<directory>tests/Integration</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
<coverage processUncoveredFiles="true">
|
||||||
|
<include>
|
||||||
|
<directory suffix=".php">autoload/Domain</directory>
|
||||||
|
<directory suffix=".php">autoload/Shared</directory>
|
||||||
|
</include>
|
||||||
|
<exclude>
|
||||||
|
<directory>vendor</directory>
|
||||||
|
<directory>tests</directory>
|
||||||
|
</exclude>
|
||||||
|
</coverage>
|
||||||
|
</phpunit>
|
||||||
13
test-debug.bat
Normal file
13
test-debug.bat
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@echo off
|
||||||
|
REM Skrypt do uruchamiania testów z PEŁNYMI szczegółami
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ================================
|
||||||
|
echo Testy DEBUG - pełne szczegóły
|
||||||
|
echo ================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
C:\xampp\php\php.exe phpunit.phar --debug %*
|
||||||
|
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
13
test-simple.bat
Normal file
13
test-simple.bat
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@echo off
|
||||||
|
REM Skrypt do uruchamiania testów - tylko kropki
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ================================
|
||||||
|
echo Testy jednostkowe shopPRO
|
||||||
|
echo ================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
C:\xampp\php\php.exe phpunit.phar %*
|
||||||
|
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
13
test.bat
Normal file
13
test.bat
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
@echo off
|
||||||
|
REM Skrypt do uruchamiania testów PHPUnit
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ================================
|
||||||
|
echo Testy jednostkowe shopPRO
|
||||||
|
echo ================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
C:\xampp\php\php.exe phpunit.phar --testdox %*
|
||||||
|
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
10
test.sh
Normal file
10
test.sh
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Skrypt do uruchamiania testów PHPUnit
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "================================"
|
||||||
|
echo " Testy jednostkowe shopPRO"
|
||||||
|
echo "================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
/c/xampp/php/php.exe phpunit.phar "$@"
|
||||||
1
tests/Integration/.gitkeep
Normal file
1
tests/Integration/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.gitkeep
|
||||||
63
tests/README.md
Normal file
63
tests/README.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Testy shopPRO
|
||||||
|
|
||||||
|
## Instalacja PHPUnit
|
||||||
|
|
||||||
|
### Opcja 1: Przez Composer (zalecane)
|
||||||
|
```bash
|
||||||
|
composer install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opcja 2: Ręcznie (jeśli nie masz Composera)
|
||||||
|
```bash
|
||||||
|
wget https://phar.phpunit.de/phpunit-9.phar
|
||||||
|
php phpunit-9.phar --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uruchamianie testów
|
||||||
|
|
||||||
|
### Wszystkie testy
|
||||||
|
```bash
|
||||||
|
composer test
|
||||||
|
# lub
|
||||||
|
vendor/bin/phpunit
|
||||||
|
```
|
||||||
|
|
||||||
|
### Konkretny plik
|
||||||
|
```bash
|
||||||
|
vendor/bin/phpunit tests/Unit/Domain/Product/ProductRepositoryTest.php
|
||||||
|
```
|
||||||
|
|
||||||
|
### Z pokryciem kodu
|
||||||
|
```bash
|
||||||
|
composer test-coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
## Anatomia testu (AAA Pattern)
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function testGetQuantityReturnsCorrectValue()
|
||||||
|
{
|
||||||
|
// Arrange - Przygotowanie
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
$mockDb->method('get')->willReturn(42);
|
||||||
|
$repository = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act - Wykonanie akcji
|
||||||
|
$quantity = $repository->getQuantity(123);
|
||||||
|
|
||||||
|
// Assert - Sprawdzenie wyniku
|
||||||
|
$this->assertEquals(42, $quantity);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Najważniejsze asercje
|
||||||
|
|
||||||
|
```php
|
||||||
|
$this->assertEquals(expected, actual); // Równość wartości
|
||||||
|
$this->assertIsInt($value); // Typ
|
||||||
|
$this->assertNull($value); // Czy null
|
||||||
|
$this->assertTrue($condition); // Czy prawda
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*Więcej: https://phpunit.de/documentation.html*
|
||||||
136
tests/Unit/Domain/Product/ProductRepositoryTest.php
Normal file
136
tests/Unit/Domain/Product/ProductRepositoryTest.php
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
namespace Tests\Unit\Domain\Product;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Domain\Product\ProductRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Testy jednostkowe dla ProductRepository
|
||||||
|
*
|
||||||
|
* Testujemy izolowaną logikę repozytorium z mockami bazy danych
|
||||||
|
*/
|
||||||
|
class ProductRepositoryTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Test pobierania ilości produktu - przypadek sukcesu
|
||||||
|
*/
|
||||||
|
public function testGetQuantityReturnsCorrectValue()
|
||||||
|
{
|
||||||
|
// Arrange (Przygotowanie)
|
||||||
|
// Tworzymy mock bazy danych
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
|
||||||
|
// Konfigurujemy mock - metoda get() zwróci 42
|
||||||
|
$mockDb->expects($this->once())
|
||||||
|
->method('get')
|
||||||
|
->with(
|
||||||
|
$this->equalTo('pp_shop_products'),
|
||||||
|
$this->equalTo('quantity'),
|
||||||
|
$this->equalTo(['id' => 123])
|
||||||
|
)
|
||||||
|
->willReturn(42);
|
||||||
|
|
||||||
|
$repository = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act (Działanie)
|
||||||
|
$quantity = $repository->getQuantity(123);
|
||||||
|
|
||||||
|
// Assert (Asercja)
|
||||||
|
$this->assertEquals(42, $quantity);
|
||||||
|
$this->assertIsInt($quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test pobierania ilości - produkt nie istnieje
|
||||||
|
*/
|
||||||
|
public function testGetQuantityReturnsNullWhenProductNotFound()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
|
||||||
|
// Medoo zwraca false gdy nie znajdzie rekordu
|
||||||
|
$mockDb->method('get')->willReturn(false);
|
||||||
|
|
||||||
|
$repository = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$quantity = $repository->getQuantity(999);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertNull($quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test pobierania produktu po ID
|
||||||
|
*/
|
||||||
|
public function testFindReturnsProductData()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
|
||||||
|
$expectedProduct = [
|
||||||
|
'id' => 123,
|
||||||
|
'name' => 'Test Product',
|
||||||
|
'quantity' => 10,
|
||||||
|
'price_brutto' => '99.99'
|
||||||
|
];
|
||||||
|
|
||||||
|
$mockDb->method('get')->willReturn($expectedProduct);
|
||||||
|
|
||||||
|
$repository = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$product = $repository->find(123);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertEquals($expectedProduct, $product);
|
||||||
|
$this->assertIsArray($product);
|
||||||
|
$this->assertEquals(123, $product['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test aktualizacji ilości produktu
|
||||||
|
*/
|
||||||
|
public function testUpdateQuantitySuccess()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
|
||||||
|
// Medoo zwraca PDOStatement w przypadku sukcesu
|
||||||
|
$mockDb->expects($this->once())
|
||||||
|
->method('update')
|
||||||
|
->with(
|
||||||
|
$this->equalTo('pp_shop_products'),
|
||||||
|
$this->equalTo(['quantity' => 50]),
|
||||||
|
$this->equalTo(['id' => 123])
|
||||||
|
)
|
||||||
|
->willReturn($this->createMock(\PDOStatement::class));
|
||||||
|
|
||||||
|
$repository = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$result = $repository->updateQuantity(123, 50);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertTrue($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test typu zwracanej wartości
|
||||||
|
*/
|
||||||
|
public function testGetQuantityReturnsInteger()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$mockDb = $this->createMock(\medoo::class);
|
||||||
|
$mockDb->method('get')->willReturn('25'); // Baza może zwrócić string
|
||||||
|
|
||||||
|
$repository = new ProductRepository($mockDb);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$quantity = $repository->getQuantity(123);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertIsInt($quantity); // Sprawdzamy czy konwersja na int zadziałała
|
||||||
|
$this->assertEquals(25, $quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
tests/bootstrap.php
Normal file
33
tests/bootstrap.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Bootstrap dla testów PHPUnit
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Załaduj Composer autoloader (jeśli istnieje)
|
||||||
|
if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
|
||||||
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
|
} else {
|
||||||
|
// Ręczny autoloader dla Domain
|
||||||
|
spl_autoload_register(function ($class) {
|
||||||
|
$prefix = 'Domain\\';
|
||||||
|
$baseDir = __DIR__ . '/../autoload/Domain/';
|
||||||
|
|
||||||
|
$len = strlen($prefix);
|
||||||
|
if (strncmp($prefix, $class, $len) !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$relativeClass = substr($class, $len);
|
||||||
|
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
|
||||||
|
|
||||||
|
if (file_exists($file)) {
|
||||||
|
require $file;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Załaduj Medoo
|
||||||
|
require_once __DIR__ . '/../libraries/medoo/medoo.php';
|
||||||
|
|
||||||
|
// Ustaw timezone
|
||||||
|
date_default_timezone_set('Europe/Warsaw');
|
||||||
BIN
updates/0.20/ver_0.237.zip
Normal file
BIN
updates/0.20/ver_0.237.zip
Normal file
Binary file not shown.
BIN
updates/0.20/ver_0.238.zip
Normal file
BIN
updates/0.20/ver_0.238.zip
Normal file
Binary file not shown.
@@ -1,3 +1,12 @@
|
|||||||
|
<b>ver. 0.238</b><br />
|
||||||
|
- NEW - refaktoryzacja: Domain\Product\ProductRepository - pierwsza klasa w nowej architekturze Domain-Driven
|
||||||
|
- NEW - Dependency Injection zamiast global variables
|
||||||
|
- UPDATE - shop\Product::get_product_quantity() używa teraz nowego repozytorium (kompatybilność zachowana)
|
||||||
|
<hr>
|
||||||
|
<b>ver. 0.237</b><br />
|
||||||
|
- NEW - automatyczne czyszczenie cache produktu po aktualizacji przez CRON (Sellasist, Apilo, Baselinker)
|
||||||
|
- UPDATE - przycisk "Wyczyść cache" w panelu administratora z obsługą AJAX i komunikatami o postępie
|
||||||
|
<hr>
|
||||||
<b>ver. 0.236</b><br />
|
<b>ver. 0.236</b><br />
|
||||||
- FIX - zabezpieczenie przed duplikatami zamówień w Apilo - automatyczne pobieranie ID zamówienia przy błędzie "idExternal już wykorzystywany"
|
- FIX - zabezpieczenie przed duplikatami zamówień w Apilo - automatyczne pobieranie ID zamówienia przy błędzie "idExternal już wykorzystywany"
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?
|
<?
|
||||||
$current_ver = 236;
|
$current_ver = 238;
|
||||||
|
|
||||||
for ($i = 1; $i <= $current_ver; $i++)
|
for ($i = 1; $i <= $current_ver; $i++)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user