├── .gitignore ├── pub ├── index.php ├── index.html ├── invoices.html └── settings.html ├── templates ├── page │ ├── not-found.php │ ├── dashboard.php │ ├── settings.php │ └── invoices.php └── base │ ├── footer.php │ ├── header.php │ └── navigation.php ├── config.php ├── src ├── ResponseInterface.php ├── Controller │ ├── ActionInterface.php │ ├── SettingsController.php │ ├── DashboardController.php │ ├── PageNotFoundController.php │ └── InvoicesController.php ├── RouterInterface.php ├── Config.php ├── RequestInterface.php ├── Entity │ └── InvoiceRepository.php ├── App.php ├── RoutesList.php ├── Database │ └── MySql.php ├── Response.php ├── Router.php └── Request.php ├── composer.json ├── composer.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | -------------------------------------------------------------------------------- /pub/index.php: -------------------------------------------------------------------------------- 1 | run(); 10 | -------------------------------------------------------------------------------- /templates/page/not-found.php: -------------------------------------------------------------------------------- 1 | 2 |

Page Not Found

3 | 4 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'host' => 'mysql', 6 | 'database' => 'invoice_system', 7 | 'username' => 'root', 8 | 'password' => 'secret', 9 | ], 10 | ]; 11 | -------------------------------------------------------------------------------- /src/ResponseInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /templates/page/dashboard.php: -------------------------------------------------------------------------------- 1 | 2 |

Dashboard

3 |
4 |

Welcome to the Invoice Management System.

5 |
6 | 7 | 8 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maxpronko/invoice-system", 3 | "type": "project", 4 | "autoload": { 5 | "psr-4": { 6 | "Invoice\\": "src/" 7 | } 8 | }, 9 | "minimum-stability": "stable", 10 | "require": { 11 | "ext-pdo": "*" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Controller/ActionInterface.php: -------------------------------------------------------------------------------- 1 | config = require __DIR__ . '/../config.php'; 12 | } 13 | 14 | public function getConfig(string $section): array 15 | { 16 | return $this->config[$section] ?? []; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/RequestInterface.php: -------------------------------------------------------------------------------- 1 | template('page/settings'); 13 | return $response; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Controller/DashboardController.php: -------------------------------------------------------------------------------- 1 | template('page/dashboard'); 13 | return $response; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Controller/PageNotFoundController.php: -------------------------------------------------------------------------------- 1 | template('page/not-found'); 14 | return $response; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Entity/InvoiceRepository.php: -------------------------------------------------------------------------------- 1 | pdo = (new MySql())->connect(); 15 | } 16 | 17 | public function load(): array 18 | { 19 | $stmt = $this->pdo->query( 20 | 'SELECT * FROM magemastery_invoice ORDER BY created_at DESC' 21 | ); 22 | return $stmt->fetchAll(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/App.php: -------------------------------------------------------------------------------- 1 | configure($router); 13 | 14 | $request = new Request(); 15 | $handler = $router->dispatch($request); 16 | 17 | $response = new Response( 18 | dirname(__DIR__) . '/templates' 19 | ); 20 | 21 | $response = $handler($request, $response); 22 | $response->render(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "e9123da151fb36b37060c0857de58d98", 8 | "packages": [], 9 | "packages-dev": [], 10 | "aliases": [], 11 | "minimum-stability": "stable", 12 | "stability-flags": [], 13 | "prefer-stable": false, 14 | "prefer-lowest": false, 15 | "platform": [], 16 | "platform-dev": [], 17 | "plugin-api-version": "2.3.0" 18 | } 19 | -------------------------------------------------------------------------------- /src/RoutesList.php: -------------------------------------------------------------------------------- 1 | get('/', DashboardController::class); 15 | $router->get('/invoices', InvoicesController::class); 16 | $router->get('/settings', SettingsController::class); 17 | $router->pageNotFound(PageNotFoundController::class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Controller/InvoicesController.php: -------------------------------------------------------------------------------- 1 | load(); 14 | $response->template( 15 | 'page/invoices', 16 | [ 17 | 'title' => 'Invoices', 18 | 'invoices' => $invoices 19 | ] 20 | ); 21 | return $response; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /templates/base/header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Invoice Management System | Mage Mastery 7 | 8 | 9 | 10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /src/Database/MySql.php: -------------------------------------------------------------------------------- 1 | getConfig('database'); 16 | self::$pdo = new PDO( 17 | sprintf( 18 | 'mysql:host=%s;dbname=%s', 19 | $config['host'], 20 | $config['database'] 21 | ), 22 | $config['username'], 23 | $config['password'] 24 | ); 25 | self::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 26 | self::$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); 27 | } 28 | return self::$pdo; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Response.php: -------------------------------------------------------------------------------- 1 | addVars($vars); 17 | $this->template = $this->templateDir . '/' . $name . '.php'; 18 | } 19 | 20 | public function addVars(array $vars): void 21 | { 22 | $this->vars = array_merge($this->vars, $vars); 23 | } 24 | 25 | public function render(): void 26 | { 27 | if (empty($this->template)) { 28 | return; 29 | } 30 | ob_start(); 31 | 32 | extract($this->vars, EXTR_SKIP); 33 | include $this->template; 34 | $output = ob_get_clean(); 35 | echo $output; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Router.php: -------------------------------------------------------------------------------- 1 | routes[self::HTTP_GET][$path] = $callback; 18 | } 19 | 20 | public function pageNotFound(string $callback): void 21 | { 22 | $this->pageNotFound = $callback; 23 | } 24 | 25 | public function dispatch(RequestInterface $request): ActionInterface 26 | { 27 | $path = $request->getPath(); 28 | $method = $request->getMethod(); 29 | 30 | $callback = $this->routes[$method][$path] ?? $this->pageNotFound; 31 | 32 | if (!$callback) { 33 | throw new Exception('Not found', 404); 34 | } 35 | 36 | return new $callback(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /templates/base/navigation.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Invoice Management System 2 | The project allows creating and managing invoice documents in a web-based application. 3 | This repository is a place where I have all source code implemented during my [YouTube](https://www.youtube.com/@MaxPronko) video series called Invoice Management System with PHP 8. 4 | 5 | ## Branches 6 | The `main` branch contains the latest changes of the Invoice Management System project. Each lesson will also have dedicated branches, `eX_start` and `eX_end`. The `X` indicates a lesson number and `e` is a shortcut for an Episode (for example `e1_end` provides source code for the Episode 1). You will find episode numbers in the video thumbnails. 7 | The `start` and `end` indicate source code start and end of an episode. If you want to follow along with me during a video episode, please start with a `eX_start` branch. 8 | 9 | # Series 10 | 1. [Invoice Management System with PHP 8](https://www.youtube.com/watch?v=zoIgpOIRJWo) 11 | 2. [Project Requirements and Setup](https://youtu.be/uSmU2KgSXC0) 12 | 3. [Simple PHP Routing for Invoice Management System](https://youtu.be/70UJa2x7Pkk) 13 | 4. [PHP Action Controller for HTTP GET Requests](https://www.youtube.com/watch?v=TCqL_OR-cgQ) 14 | 5. [Templating for Invoice Management System with PHP](https://youtu.be/DFPLTYq8boI) 15 | 6. [Implementing Page Not Found PHP handler](https://www.youtube.com/watch?v=EIeQhNavy0k) 16 | 7. [Router with Dynamic Params in PHP 8](https://www.youtube.com/watch?v=JYeU4LrWUjY) 17 | 8. [Docker Introduction for PHP Developers](https://www.youtube.com/watch?v=ox6JlF1kyzk) 18 | 9. [MySQL Database Connection via PHP PDO](https://www.youtube.com/watch?v=z9UiCfN8Tdk) 19 | 20 | -------------------------------------------------------------------------------- /src/Request.php: -------------------------------------------------------------------------------- 1 | server = $_SERVER; 13 | $this->addParams($this->getQueryParams()); 14 | $this->addParams($_POST); 15 | } 16 | 17 | private function addParams(array $params): void 18 | { 19 | $this->params = array_merge($this->params, $params); 20 | } 21 | 22 | public function getServer(string $name): mixed 23 | { 24 | return $this->server[$name] ?? null; 25 | } 26 | 27 | public function getUri(): array 28 | { 29 | return parse_url($this->getServer('REQUEST_URI')); 30 | } 31 | 32 | public function getPath(): string 33 | { 34 | $uri = $this->getUri(); 35 | return $uri['path']; 36 | } 37 | 38 | public function getMethod(): string 39 | { 40 | return $this->getServer('REQUEST_METHOD'); 41 | } 42 | 43 | public function getParam(string $name): mixed 44 | { 45 | return $this->params[$name] ?? null; 46 | } 47 | 48 | public function getParams(): array 49 | { 50 | return $this->params; 51 | } 52 | 53 | public function getQuery(): ?string 54 | { 55 | $uri = $this->getUri(); 56 | return $uri['query'] ?? null; 57 | } 58 | 59 | public function getQueryParams(): array 60 | { 61 | $params = []; 62 | if ($this->getQuery()) { 63 | parse_str($this->getQuery(), $params); 64 | } 65 | return $params; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /templates/page/settings.php: -------------------------------------------------------------------------------- 1 | 2 |

Settings

3 |
4 | 5 |
6 |
7 |
8 |
9 | 10 |
11 | 14 |
15 |
16 |
17 | 18 |
19 | 22 |
23 |
24 |
25 | 26 |
27 | 28 |
29 |
30 |
31 |
32 |
33 |
34 | 35 | -------------------------------------------------------------------------------- /templates/page/invoices.php: -------------------------------------------------------------------------------- 1 | 7 |

8 | New Invoice 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 45 | 46 | 47 | 48 | 49 |
NumberToDateDue DatePaidDue PaidStatus
25 | There are no invoices. 26 |
38 | 39 | Awaiting Payment 40 | Add Payment 41 | 42 | Paid 43 | 44 |
50 | 51 | -------------------------------------------------------------------------------- /pub/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Invoice Management System | Mage Mastery 7 | 8 | 9 | 10 | 11 | 35 |
36 |

Dashboard

37 |
38 |

Welcome to the Invoice Management System.

39 |
40 |
41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /pub/invoices.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Invoice Management System | Mage Mastery 7 | 8 | 9 | 10 | 11 | 35 |
36 |

Invoices

37 | New Invoice 38 |
39 |

There are no invoices.

40 |
41 |
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /pub/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Invoice Management System | Mage Mastery 7 | 8 | 9 | 10 | 11 | 35 |
36 |

Settings

37 |
38 | 39 |
40 |
41 |
42 |
43 | 44 |
45 | 48 |
49 |
50 |
51 | 52 |
53 | 56 |
57 |
58 |
59 | 60 |
61 | 62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | 70 | 71 | 72 | --------------------------------------------------------------------------------