I am following Lopez' "Learning PHP 7" book and in Ch. 5 on MVC I would be more than grateful to learn what is wrong here.
When in init.php I comment out - see the nine-line comment in init.php - the autoloader function, which should not be needed any more, Lopez writes, I get this error:
PHP Fatal error: Uncaught Error: Class 'Bookstore\Utils\Config' not found in /home/petr/Documents/workspace/bookstore/init.php:25 Stack trace: ...
Line 25 in init.php which is shown below entirely is
$dbConfig = Config::getInstance()->get('db');
(When installing PHP, MySQL, Composer, and Twig I did not use Vagrant [had installed PHP, MySQL, and Apache on Ubuntu before starting with this book and have added Composer w. Twig via Synaptic today]. I use PHP's default web server developing this project.)
The project structure is like this:
init.php is like so:
<?php
use Bookstore\Domain\Customer\Basic;
use Bookstore\Domain\Customer\Premium;
use Bookstore\Domain\Customer\CustomerFactory;
use Bookstore\Domain\Customer;
use Bookstore\Domain\Payer;
use Bookstore\Domain\Person;
use Bookstore\Domain\Book;
use Bookstore\Utils\Config;
use Bookstore\Utils\Unique;
use Bookstore\Exceptions\InvalidIdException;
use Bookstore\Exceptions\ExceededMaxAllowedException;
// function autoloader($classname) {
// $lastSlash = strpos($classname, '\\') + 1;
// $classname = substr($classname, $lastSlash);
// $directory = str_replace('\\', '/', $classname);
// $filename = __DIR__ . '/' . $directory . '.php';
// require_once($filename);
// }
//
// spl_autoload_register('autoloader');
$dbConfig = Config::getInstance()->get('db');
$db = new PDO(
'mysql:host=127.0.0.1;dbname=bookstore',
$dbConfig['user'],
$dbConfig['password']
);
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
function addBook(int $id, int $amount = 1): void {
$db = new PDO(
'mysql:host=127.0.0.1;dbname=bookstore',
'root',
''
);
$query = 'UPDATE book SET stock = stock + :n WHERE id = :id';
$statement = $db->prepare($query);
$statement->bindValue('id', $id);
$statement->bindValue('n', $amount);
if (!$statement->execute()) {
throw new Exception($statement->errorInfo()[2]);
}
}
function addSale(int $userId, array $bookIds): void {
$db = new PDO(
'mysql:host=127.0.0.1;dbname=bookstore',
'root',
''
);
$db->beginTransaction();
try {
$query = 'INSERT INTO sale (customer_id, date) '
. 'VALUES(:id, NOW())';
$statement = $db->prepare($query);
if (!$statement->execute(['id' => $userId])) {
throw new Exception($statement->errorInfo()[2]);
}
$saleId = $db->lastInsertId();
$query = 'INSERT INTO sale_book (book_id, sale_id) '
. 'VALUES (:book, :sale)';
$statement = $db->prepare($query);
$statement->bindValue('sale', $saleId);
foreach ($bookIds as $bookId) {
$statement->bindValue('book', $bookId);
if (!$statement->execute()) {
throw new Exception($statement->errorInfo()[2]);
}
}
$db->commit();
}
catch (Exception $e) {
$db->rollBack();
throw $e;
}
}
try {
addSale(1, [3, 7]);
}
catch (Exception $e) {
echo 'Error adding sale: ' . $e->getMessage();
}
composer.json is this file:
{
"require": {
"monolog/monolog": "^1.17",
"twig/twig": "^1.23"
},
"autoload": {
"psr-4": {
"Bookstore\\": "bookstore"
}
}
}
And this is the Config.php:
<?php
namespace Bookstore\Utils;
use Bookstore\Exceptions\NotFoundException;
class Config {
private static $data; // private $data;
private static $instance;
private function __construct() { // public function __construct() {
$json = file_get_contents(__DIR__ . '/../config/app.json');
self::$data = json_decode($json, true); // $this->data = json_decode($json, true);
}
public static function getInstance() {
if (self::$instance == null) {
self::$instance = new Config();
}
return self::$instance;
}
public static function get($key) { // public function get($key) {
if (!isset(self::$data[$key])) { // if (!isset($this->data[$key])) {
throw new NotFoundException("Key $key not in config.");
}
return self::$data[$key]; // return $this->data[$key];
}
}
I wonder how to make the project work with the function autoloader
removed from init.php. Thank you.
You need an autoloader to magically load the classes. As you have defined composer.json
with your dependencies and your PSR-4 autoloading rule, I would recommend to use Composers autoloader.
Your composer.json
configuration has a small error: The PSR-4 autoloader tries to load the classes within the Bookstore
namespace from a sub-folder called bookstore
. These classes are placed in your project's root directory. Therefore the autoloader has to point to that directory instead by leaving the directory path empty:
{
"require": {
"monolog/monolog": "^1.17",
"twig/twig": "^1.23"
},
"autoload": {
"psr-4": {
"Bookstore\\": ""
}
}
}