Zwrócenia wyniku zapytania SQL jako tablicę obiektów klasy Task

0

Poznaję rozszerzenie PDO i chciałbym zwrócić wynik zapytania jako tablicę obiektów o określonej klasie, którą stworzyłem. Niestety samemu nie potrafię dojść do rozwiązania więc proszę o drobną pomoc :)

Warning: PDOStatement::fetchAll(): SQLSTATE[HY000]: General error: could not call class constructor in C:\xampp\htdocs\tutorial\09\DatabaseManager.php on line 26
Fatal error: Uncaught ArgumentCountError: Too few arguments to function Task::__construct(), 0 passed and exactly 3 expected in C:\xampp\htdocs\tutorial\09\Task.php:8 Stack trace: #0 [internal function]: Task->__construct() #1 C:\xampp\htdocs\tutorial\09\DatabaseManager.php(26): PDOStatement->fetchAll(8, 'Task') #2 C:\xampp\htdocs\tutorial\09\index.php(9): DatabaseManager->executeQuery('SELECT * FROM t...') #3 {main} thrown in C:\xampp\htdocs\tutorial\09\Task.php on line 8

Task.php

<?php

class Task {
    private $id;
    private $description;
    private $completed;

    public function __construct(int $id, string $description, bool $completed) {
        $this->id = $id;
        $this->description = $description;
        $this->completed = $completed;
    }

    public function complete() {
        $this->completed = true;
    }

    public function isCompleted(): bool {
        return $this->completed;
    }

    public function getDescription(): string {
        return $this->description;
    }

    public function getCompleted(): string {
        return $this->completed ? 'yes' : 'no';
    }
}

DatabaseManager.php

<?php

class DatabaseManager {
    private $dsn;
    private $username;
    private $password;
    private $pdo;

    public function __construct(string $dsn, string $username, string $password) {
        $this->dsn = $dsn;
        $this->username = $username;
        $this->password = $password;
    }

    public function connect() {
        try {
            $this->pdo = new PDO($this->dsn, $this->username, $this->password);
        } catch (PDOException $e) {
            $e->getMessage();
        }
    }

    public function executeQuery(string $query): array {
        $statement = $this->pdo->prepare($query);
        $statement->execute();
        $result = $statement->fetchAll(PDO::FETCH_CLASS, 'Task');

        return $result;
    }
}

index.php

<?php

require 'Task.php';
require 'DatabaseManager.php';

$databaseManager = new DatabaseManager('mysql:host=127.0.0.1;dbname=mytodo;', 'root', '');
$databaseManager->connect();

$tasks = $databaseManager->executeQuery('SELECT * FROM todos');

require 'index.view.php';

index.view.php

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>09</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
  <style>
    body { padding: 2.5rem; }
  </style>
</head>
<body>

  <main class="container">
    <pre><?php var_dump($tasks); ?></pre>
    <pre><?= $tasks[0]->getDescription(); ?></pre>
    <pre><?= $tasks[0]->getCompleted(); ?></pre>
  </main>

</body>
</html>

Struktura tabeli todos
todos.png

0

fetchAll nie bawi się w konstruktory - musiałbyś zrobić wszystkie pola publiczne i wywalić konstruktor ;-)

Na marginesie: proces transformacji danych z bazy na obiekty aplikacji nazywa się hydration.

Na marginesie #2: Twoja klasa DatabaseManager powinna się raczej nazywać DatabaseConnection. Unikaj nazywania klas w stylu CośtamManager, bo rozmywa to ich odpowiedzialność i łatwo potem pakuje się dziesiątki niepotrzebnych metod do takiej klasy, zamiast wydzielać do odrębnych klas.

0
Patryk27 napisał(a):

fetchAll nie bawi się w konstruktory - musiałbyś zrobić wszystkie pola publiczne i wywalić konstruktor ;-)

Faktycznie, po usunięciu konstruktora program zadziałał prawidłowo.

Patryk27 napisał(a):

Na marginesie #2: Twoja klasa DatabaseManager powinna się raczej nazywać DatabaseConnection. Unikaj nazywania klas w stylu CośtamManager, bo rozmywa to ich odpowiedzialność i łatwo potem pakuje się dziesiątki niepotrzebnych metod do takiej klasy, zamiast wydzielać do odrębnych klas.

Jasne, na przyszłość będę pamiętał.

0

@Patryk27: przepraszam za double post, ale mam jeszcze jedno pytanie, a za szybko wysłałem odpowiedź :)

Jak usunąłem konstruktor z klasy Task to jak wspomniałem wcześniej, program zadziałał prawidłowo jak widać poniżej.

array(2) {
  [0]=>
  object(Task)#4 (3) {
    ["id":"Task":private]=>
    string(1) "1"
    ["description":"Task":private]=>
    string(23) "Completed PHP Laracasts"
    ["completed":"Task":private]=>
    string(1) "0"
  }
  [1]=>
  object(Task)#5 (3) {
    ["id":"Task":private]=>
    string(1) "2"
    ["description":"Task":private]=>
    string(35) "Create database and insert few rows"
    ["completed":"Task":private]=>
    string(1) "1"
  }
}
Completed PHP Laracasts
no

Klasa Task jest bez konstruktora i czy to oznacza, że nie będę mógł tworzyć obiektów tej klasy za pomocą konstrukcji new Task(...)? Jak dobrze zrobić aby jednocześnie móc pobierać dane z bazy i mieć możliwość "ręcznego" tworzenia obiektów Task?

0
// Podejście #1: publiczne pola
$task = new Task();
$task->description = 'foo';
// Podejście #2: publiczne settery
$task = new Task();
$task->setDescription('foo');
// Podejście #3: wykorzystanie faktu, że masz dostęp do prywatnych pól z kontekstu klasy
class Task {

  private $description;

  public function getDescription(): string {
    return $this->description;
  }

  static public function fromArray(array $data): Task {
    $task = new Task();
    $task->description = $data['description'];

    return $task;
  }

}

$task = Task::fromArray([
  'description' => 'foo',
]);

Btw, klasy w PHP mają domyślnie domniemany publiczny konstruktor w formie public function __construct() { } - więc nawet gdy sam nie utworzysz konstruktora, nadal będziesz w stanie tworzyć instancje danej klasy.

Btw #2, istnieje jeszcze wiele innych, bardziej pomysłowych podejść do tworzenia instancji klas (unserialize czy Closure::bind ;-)), ale to już są hacki - wymienione przeze mnie sposoby powinny Ci wystarczyć na każdy przypadek.

0

Czego nie rozumiesz w komunikacie Too few arguments to function Task::__construct(), 0 passed and exactly 3 expected ?

0

@axelbest: Zrozumiałem komunikat, ale nie zrozumiałem dlaczego się pojawił skoro podany był konstruktor :)

0

Tworzyłes obiekt Task bez parametrów mimo ze konstruktor wymagał trzech i tyle w temacie.

0 użytkowników online, w tym zalogowanych: 0, gości: 0