feat: Refactor article handling and introduce ArticleRepository

- Introduced Domain\Article\ArticleRepository for better data access.
- Migrated article_edit functionality to admin\Controllers\ArticlesController.
- Updated admin\factory\Articles::article_details() to use the new repository.
- Marked legacy methods in admin\controls as @deprecated for clarity.
- Updated changelog and versioning to reflect changes in version 0.242.
This commit is contained in:
2026-02-06 08:41:34 +01:00
parent f1c7019cc5
commit e33978e1bb
17 changed files with 333 additions and 27 deletions

View File

@@ -1 +1 @@
{"version":1,"defects":[],"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.001,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsPromoPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularWhenPromoIsHigher":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsProductName":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveReturnsBool":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveReturnsBool":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasUnarchiveMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testUnarchiveMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorRequiresProductRepository":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsBannerWithTranslations":0.004,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testDeleteReturnsTrue":0.002,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveInsertsNewBanner":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithRedis":0.001,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheRedisUnavailable":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithoutRedis":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheReturnStructure":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testCanBeInstantiated":0.001,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasSaveSettingsMethod":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasGetSettingsMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheAjaxMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasViewMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testIsNotAbstract":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testActionMethodReturnTypes":0}}
{"version":1,"defects":[],"times":{"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsCorrectValue":0.001,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsNullWhenProductNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testFindReturnsProductData":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUpdateQuantitySuccess":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsPromoPrice":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsRegularWhenPromoIsHigher":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetPriceReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsProductName":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetNameReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testGetQuantityReturnsInteger":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveUpdatesProductAndChildren":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testUnarchiveReturnsBool":0,"Tests\\Unit\\Domain\\Product\\ProductRepositoryTest::testArchiveReturnsBool":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testHasUnarchiveMethod":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testUnarchiveMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ProductArchiveControllerTest::testConstructorRequiresProductRepository":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsBannerWithTranslations":0.001,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testDeleteReturnsTrue":0.002,"Tests\\Unit\\Domain\\Banner\\BannerRepositoryTest::testSaveInsertsNewBanner":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithRedis":0.001,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheRedisUnavailable":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheWithoutRedis":0,"Tests\\Unit\\Domain\\Cache\\CacheRepositoryTest::testClearCacheReturnStructure":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testCanBeInstantiated":0.001,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasSaveSettingsMethod":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testHasGetSettingsMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testConstructorAcceptsRepository":0.001,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasClearCacheAjaxMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasSaveMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testHasViewMethod":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testIsNotAbstract":0,"Tests\\Unit\\admin\\Controllers\\SettingsControllerTest::testActionMethodReturnTypes":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testCanCreateController":0.001,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasListMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testListMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testHasEditMethod":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testEditMethodReturnType":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorAcceptsRepository":0,"Tests\\Unit\\admin\\Controllers\\ArticlesControllerTest::testConstructorRequiresArticleRepository":0,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsArticleWithRelations":0.003,"Tests\\Unit\\Domain\\Article\\ArticleRepositoryTest::testFindReturnsNullWhenArticleDoesNotExist":0}}

View File

@@ -92,16 +92,49 @@ Artykuły.
| id | PK |
| status | -1 = archiwum, 0 = nieaktywny, 1 = aktywny |
**Używane w:** `admin\controls\ArticlesArchive`
**Używane w:** `admin\controls\ArticlesArchive`, `Domain\Article\ArticleRepository::find()`
## pp_articles_pages
Strony artykułów.
| Kolumna | Opis |
|---------|------|
| article_id | FK do pp_articles |
| page_id | FK do strony (pp_pages) |
| o | Kolejność |
**Używane w:** `Domain\Article\ArticleRepository::find()`
## pp_articles_langs
Tłumaczenia artykułów.
| Kolumna | Opis |
|---------|------|
| article_id | FK do pp_articles |
| lang_id | ID języka (np. 'pl') |
| title | Tytuł artykułu |
| seo_link | Link SEO artykułu |
**Używane w:** `Domain\Article\ArticleRepository::find()`
## pp_articles_images
Zdjęcia artykułów.
| Kolumna | Opis |
|---------|------|
| article_id | FK do pp_articles |
| src | Ścieżka do pliku |
| o | Kolejność |
| id | PK (używane też do sortowania DESC) |
**Używane w:** `Domain\Article\ArticleRepository::find()`
## pp_articles_files
Pliki artykułów.
| Kolumna | Opis |
|---------|------|
| article_id | FK do pp_articles |
| src | Ścieżka do pliku |
**Używane w:** `Domain\Article\ArticleRepository::find()`

View File

@@ -246,10 +246,18 @@ tests/
│ └── ProductArchiveControllerTest.php # 6 testów
└── Integration/
```
**Łącznie: 39 testów, 73 asercji**
**Ĺ<EFBFBD>Ä…cznie: 48 testów, 91 asercji**
## Ostatnie modyfikacje
### 2026-02-06: Migracja Articles::article_edit do DI (ver. 0.242)
- **NOWE:** `Domain\Article\ArticleRepository` - repozytorium artykuĹĂłw (`find()`)
- **UPDATE:** `admin\Controllers\ArticlesController` - konstruktor DI + `edit()` uĹĽywa repozytorium
- **UPDATE:** Router `admin\Site` - factory dla `ArticlesController` z `ArticleRepository`
- **UPDATE:** `admin\factory\Articles::article_details()` deleguje do `Domain\Article\ArticleRepository`
- **UPDATE:** Stare kontrolery `admin\controls\Articles|Banners|Settings` - metody przejęte przez nowe kontrolery oznaczone `@deprecated`
- Testy: 48 testĂłw, 91 asercji
### 2026-02-06: Migracja ProductArchive (ver. 0.241)
- **NOWE:** `admin\Controllers\ProductArchiveController` - kontroler archiwum produktĂłw z DI
- **NOWE:** `ProductRepository::archive()`, `unarchive()` - operacje archiwizacji w repozytorium
@@ -258,7 +266,7 @@ tests/
- **FIX:** BrakujÄ…cy `archive = 1` w branchu bez wyszukiwania
- **CLEANUP:** Usunięto zbędny JS z szablonu archiwum (apilo, baselinker, duplikowanie, edycja cen)
- Stary kontroler `admin\controls\Archive` zachowany jako fallback
- Testy: 39 testów, 73 asercji (+10 nowych)
- Testy: 48 testów, 91 asercji (+10 nowych)
### 2026-02-05: Migracja Settings + Cache (ver. 0.240)
- **NOWE:** `Domain\Settings\SettingsRepository` - repozytorium ustawień (fasada → factory)
@@ -293,4 +301,5 @@ tests/
- Metoda `clear_product_cache()` w klasie S
---
*Dokument aktualizowany: 2026-02-05*
*Dokument aktualizowany: 2026-02-06*

View File

@@ -174,6 +174,16 @@ grep -r "Product::getQuantity" .
- Stara factory `admin\factory\Banners` zachowana bez zmian (fallback)
- Aktualizacja: ver. 0.239
- **Articles** (migracja kontrolera - etap edit/details)
- ✅ ArticleRepository::find() - **ZMIGROWANE** (2026-02-06) 🎉
- Nowa klasa: `Domain\Article\ArticleRepository` (find: artykul + relacje)
- Nowy kontroler: `admin\Controllers\ArticlesController` (DI, instancyjny)
- Zmigrowana akcja: `article_edit` -> `edit` (mapowanie w `admin\Site::$actionMap`)
- Kompatybilność: `admin\factory\Articles::article_details()` deleguje do nowego repozytorium
- Legacy cleanup: metody przejęte przez nowe kontrolery oznaczone `@deprecated` w `admin\controls\Articles|Banners|Settings`
- Testy: `tests/Unit/Domain/Article/ArticleRepositoryTest.php`, `tests/Unit/admin/Controllers/ArticlesControllerTest.php`
- Aktualizacja: ver. 0.242
- **Settings** (migracja kontrolera - krok pośredni)
- ✅ SettingsRepository - **ZMIGROWANE** (2026-02-05) 🎉
- Nowa klasa: `Domain\Settings\SettingsRepository` (saveSettings, getSettings)
@@ -217,7 +227,7 @@ tests/
│ └── ProductArchiveControllerTest.php # 6 testów
└── Integration/
```
**Łącznie: 39 testów, 73 asercji**
**Łącznie: 48 testów, 91 asercji**
### Przykład testu
```php
@@ -305,4 +315,4 @@ vendor/bin/phpstan analyse autoload/Domain
---
*Rozpoczęto: 2025-02-05*
*Ostatnia aktualizacja: 2026-02-05*
*Ostatnia aktualizacja: 2026-02-06*

View File

@@ -11,6 +11,9 @@ Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesią
- `changelog.php` - historia zmian
- `versions.php` - konfiguracja wersji (zmienna `$current_ver`)
### Zasada pakowania plików
- Do paczek aktualizacji **nie dodajemy plików `*.md`** (dokumentacja jest tylko wewnętrzna/deweloperska).
## Procedura tworzenia nowej aktualizacji
### 1. Określ numer wersji

View File

@@ -0,0 +1,43 @@
<?php
namespace Domain\Article;
/**
* Repository odpowiedzialny za dostep do danych artykulow
*/
class ArticleRepository
{
private $db;
public function __construct($db)
{
$this->db = $db;
}
/**
* Pobiera artykul po ID wraz z tlumaczeniami, obrazami, plikami i powiazanymi stronami
*/
public function find(int $articleId): ?array
{
$article = $this->db->get('pp_articles', '*', ['id' => $articleId]);
if (!$article) {
return null;
}
$results = $this->db->select('pp_articles_langs', '*', ['article_id' => $articleId]);
if (is_array($results)) {
foreach ($results as $row) {
$article['languages'][$row['lang_id']] = $row;
}
}
$article['images'] = $this->db->select('pp_articles_images', '*', [
'article_id' => $articleId,
'ORDER' => ['o' => 'ASC', 'id' => 'DESC']
]);
$article['files'] = $this->db->select('pp_articles_files', '*', ['article_id' => $articleId]);
$article['pages'] = $this->db->select('pp_articles_pages', 'page_id', ['article_id' => $articleId]);
return $article;
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace admin\Controllers;
use Domain\Article\ArticleRepository;
class ArticlesController
{
private ArticleRepository $repository;
public function __construct(ArticleRepository $repository)
{
$this->repository = $repository;
}
/**
* Lista artykulow
*/
public function list(): string
{
return \admin\view\Articles::articles_list();
}
/**
* Edycja artykulu
*/
public function edit(): string
{
global $user;
if (!$user) {
header('Location: /admin/');
exit;
}
\admin\factory\Articles::delete_nonassigned_images();
\admin\factory\Articles::delete_nonassigned_files();
return \Tpl::view('articles/article-edit', [
'article' => $this->repository->find((int)\S::get('id')),
'menus' => \admin\factory\Pages::menus_list(),
'languages' => \admin\factory\Languages::languages_list(),
'layouts' => \admin\factory\Layouts::layouts_list(),
'user' => $user
]);
}
}

View File

@@ -199,6 +199,13 @@ class Site
return self::$newControllers;
self::$newControllers = [
'Articles' => function() {
global $mdb;
return new \admin\Controllers\ArticlesController(
new \Domain\Article\ArticleRepository( $mdb )
);
},
'Banners' => function() {
global $mdb;
@@ -247,6 +254,7 @@ class Site
*/
private static $actionMap = [
'view_list' => 'list',
'article_edit' => 'edit',
'banner_edit' => 'edit',
'banner_save' => 'save',
'banner_delete' => 'delete',

View File

@@ -39,6 +39,10 @@ class Articles
exit;
}
/**
* @deprecated Routing kieruje do admin\Controllers\ArticlesController::edit().
* Ta metoda pozostaje tylko jako fallback dla starej architektury.
*/
public static function article_edit() {
global $user;
@@ -59,9 +63,13 @@ class Articles
] );
}
/**
* @deprecated Routing kieruje do admin\Controllers\ArticlesController::list().
* Ta metoda pozostaje tylko jako fallback dla starej architektury.
*/
public static function view_list()
{
return \admin\view\Articles::articles_list();
}
}
?>
?>

View File

@@ -9,6 +9,10 @@ namespace admin\controls;
*/
class Banners
{
/**
* @deprecated Routing kieruje do admin\Controllers\BannerController::delete().
* Ta metoda pozostaje tylko jako fallback dla starej architektury.
*/
public static function banner_delete()
{
if ( \admin\factory\Banners::banner_delete( \S::get( 'id' ) ) )
@@ -17,6 +21,10 @@ class Banners
exit;
}
/**
* @deprecated Routing kieruje do admin\Controllers\BannerController::save().
* Ta metoda pozostaje tylko jako fallback dla starej architektury.
*/
public static function banner_save()
{
$response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania baneru wystąpił błąd. Proszę spróbować ponownie.' ];
@@ -30,6 +38,10 @@ class Banners
exit;
}
/**
* @deprecated Routing kieruje do admin\Controllers\BannerController::edit().
* Ta metoda pozostaje tylko jako fallback dla starej architektury.
*/
public static function banner_edit()
{
return \admin\view\Banners::banner_edit(
@@ -40,6 +52,10 @@ class Banners
);
}
/**
* @deprecated Routing kieruje do admin\Controllers\BannerController::list().
* Ta metoda pozostaje tylko jako fallback dla starej architektury.
*/
public static function view_list()
{
return \admin\view\Banners::banners_list();

View File

@@ -3,6 +3,10 @@ namespace admin\controls;
class Settings
{
/**
* @deprecated Routing kieruje do admin\Controllers\SettingsController::clearCache().
* Ta metoda pozostaje tylko jako fallback dla starej architektury.
*/
static public function clear_cache()
{
\S::delete_dir( '../temp/' );
@@ -17,6 +21,10 @@ class Settings
exit;
}
/**
* @deprecated Routing kieruje do admin\Controllers\SettingsController::clearCacheAjax().
* Ta metoda pozostaje tylko jako fallback dla starej architektury.
*/
static public function clear_cache_ajax()
{
try
@@ -41,6 +49,10 @@ class Settings
exit;
}
/**
* @deprecated Routing kieruje do admin\Controllers\SettingsController::save().
* Ta metoda pozostaje tylko jako fallback dla starej architektury.
*/
public static function settings_save()
{
$values = json_decode( \S::get( 'values' ), true );
@@ -88,6 +100,10 @@ class Settings
exit;
}
/**
* @deprecated Routing kieruje do admin\Controllers\SettingsController::view().
* Ta metoda pozostaje tylko jako fallback dla starej architektury.
*/
public static function view()
{
return \Tpl::view( 'settings/settings', [
@@ -96,4 +112,4 @@ class Settings
] );
}
}
?>
?>

View File

@@ -123,19 +123,8 @@ class Articles
public static function article_details( $article_id )
{
global $mdb;
if ( $article = $mdb -> get( 'pp_articles', '*', [ 'id' => (int)$article_id ] ) )
{
$results = $mdb -> select( 'pp_articles_langs', '*', [ 'article_id' => (int)$article_id ] );
if ( is_array( $results ) ) foreach ( $results as $row )
$article['languages'][ $row['lang_id'] ] = $row;
$article['images'] = $mdb -> select( 'pp_articles_images', '*', [ 'article_id' => (int)$article_id, 'ORDER' => [ 'o' => 'ASC', 'id' => 'DESC' ] ] );
$article['files'] = $mdb -> select( 'pp_articles_files', '*', [ 'article_id' => (int)$article_id ] );
$article['pages'] = $mdb -> select( 'pp_articles_pages', 'page_id', [ 'article_id' => (int)$article_id ] );
}
return $article;
$repository = new \Domain\Article\ArticleRepository( $mdb );
return $repository->find( (int)$article_id );
}
public static function max_order()
@@ -469,4 +458,4 @@ class Articles
$mdb -> delete( 'pp_articles_images', [ 'article_id' => null ] );
}
}
?>
?>

View File

@@ -0,0 +1,60 @@
<?php
namespace Tests\Unit\Domain\Article;
use PHPUnit\Framework\TestCase;
use Domain\Article\ArticleRepository;
class ArticleRepositoryTest extends TestCase
{
public function testFindReturnsArticleWithRelations(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_articles', '*', ['id' => 7])
->willReturn(['id' => 7, 'status' => 1]);
$mockDb->expects($this->exactly(4))
->method('select')
->willReturnOnConsecutiveCalls(
[
['lang_id' => 'pl', 'title' => 'Artykul'],
['lang_id' => 'en', 'title' => 'Article'],
],
[
['id' => 10, 'src' => '/img/a.jpg']
],
[
['id' => 20, 'src' => '/files/a.pdf']
],
[1, 2]
);
$repository = new ArticleRepository($mockDb);
$article = $repository->find(7);
$this->assertIsArray($article);
$this->assertEquals(7, $article['id']);
$this->assertArrayHasKey('languages', $article);
$this->assertEquals('Artykul', $article['languages']['pl']['title']);
$this->assertCount(1, $article['images']);
$this->assertCount(1, $article['files']);
$this->assertEquals([1, 2], $article['pages']);
}
public function testFindReturnsNullWhenArticleDoesNotExist(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_articles', '*', ['id' => 999])
->willReturn(false);
$mockDb->expects($this->never())->method('select');
$repository = new ArticleRepository($mockDb);
$article = $repository->find(999);
$this->assertNull($article);
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Tests\Unit\admin\Controllers;
use PHPUnit\Framework\TestCase;
use admin\Controllers\ArticlesController;
use Domain\Article\ArticleRepository;
class ArticlesControllerTest extends TestCase
{
private $mockRepository;
private $controller;
protected function setUp(): void
{
$this->mockRepository = $this->createMock(ArticleRepository::class);
$this->controller = new ArticlesController($this->mockRepository);
}
public function testCanCreateController(): void
{
$this->assertInstanceOf(ArticlesController::class, $this->controller);
}
public function testConstructorAcceptsRepository(): void
{
$controller = new ArticlesController($this->mockRepository);
$this->assertInstanceOf(ArticlesController::class, $controller);
}
public function testHasListMethod(): void
{
$this->assertTrue(method_exists($this->controller, 'list'));
}
public function testHasEditMethod(): void
{
$this->assertTrue(method_exists($this->controller, 'edit'));
}
public function testListMethodReturnType(): void
{
$reflection = new \ReflectionClass($this->controller);
$this->assertEquals('string', (string)$reflection->getMethod('list')->getReturnType());
}
public function testEditMethodReturnType(): void
{
$reflection = new \ReflectionClass($this->controller);
$this->assertEquals('string', (string)$reflection->getMethod('edit')->getReturnType());
}
public function testConstructorRequiresArticleRepository(): void
{
$reflection = new \ReflectionClass(ArticlesController::class);
$constructor = $reflection->getConstructor();
$params = $constructor->getParameters();
$this->assertCount(1, $params);
$this->assertEquals('Domain\Article\ArticleRepository', $params[0]->getType()->getName());
}
}

BIN
updates/0.20/ver_0.242.zip Normal file

Binary file not shown.

View File

@@ -1,4 +1,8 @@
<b>ver. 0.241</b><br />
<b>ver. 0.242</b><br />
- NEW - refaktoryzacja: Domain\Article\ArticleRepository + migracja article_edit do admin\Controllers\ArticlesController (DI)
- UPDATE - admin\factory\Articles::article_details() deleguje do nowego repozytorium (kompatybilność zachowana)
- UPDATE - metody przejęte przez nowe kontrolery oznaczone jako @deprecated w legacy kontrolerach admin\controls
<hr><b>ver. 0.241</b><br />
- NEW - refaktoryzacja: admin\Controllers\ProductArchiveController - archiwum produktĂłw z DI
- NEW - ProductRepository::archive(), unarchive() - operacje archiwizacji w repozytorium
- FIX - naprawiono SQL w liĹcie archiwum (puste wyszukiwanie filtrowaĹo wszystkie wyniki)
@@ -298,4 +302,4 @@
- FIX - poprawa generowania plikĂłw WEBP
<hr>
<b>ver. 0.142</b><br />
- FIX - poprawa adresu strony ównej
- FIX - poprawa adresu strony głównej

View File

@@ -1,5 +1,5 @@
<?
$current_ver = 241;
$current_ver = 242;
for ($i = 1; $i <= $current_ver; $i++)
{
@@ -43,4 +43,4 @@ else
{
foreach ($versions as $ver)
echo $ver . PHP_EOL;
}
}