Skip to content

Commit 15dddc1

Browse files
committed
Update
1 parent 0e83b40 commit 15dddc1

File tree

1,372 files changed

+1224
-137869
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

1,372 files changed

+1224
-137869
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.idea
2+
.env
3+
vendor

.phpunit.result.cache

-1
This file was deleted.

README.md

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#### The exercise for the PHP course "Learn PHP The Right Way" lesson 2.33.
2+
3+
---
4+
#### Course Playlist
5+
https://www.youtube.com/watch?v=sVbEyFZKgqk&list=PLr3d3QYzkw2xabQRUpcZ_IBk9W50M9pe-
6+
7+
---
8+
#### Summary
9+
At the end of the section 1 of the course we did a mini project & exercise where we built a transaction file importer. We did it procedural way & also read the files from directory instead of letting user upload them. This exercise is for you to implement the same thing but use OOP as well as database to store the imported transactions & then display them on the screen like this:
10+
![Sample Output](result.png)
11+
12+
I have provided you with the skeleton of code that you can work with, it is what we already covered in the second section of the course (in the last few lessons). You can refer to [this video](https://youtu.be/iCKzIIE4w5E) to understand the structure of the code. Note that you can write the whole thing on your own & don't use this structure at all, the goal is to simply accept a file upload of transactions, save them in transactions table & color code expense/income when displaying the transactions table. You can look at the code that I wrote & explained in [this video](https://youtu.be/MOsolLaVnsI) & convert it into OOP if you wish.
13+
14+
---
15+
#### Instructions
16+
1. Clone this repository to your local or download it.
17+
2. If you are using docker you can `cd` into the docker directory & run `docker-compose up -d`. If you are using something else like XAMPP just make sure you have Web Server (Apache), PHP & MySQL running.
18+
* Please note that **PHP 8** is required if you want to use the skeleton that I am providing. You will need to adjust the code to make it work for lower PHP versions.
19+
3. Create a `.env` file by copying variables from `.env.example`. Fill in those values in `.env` file.
20+
4. Make sure that whatever database name you enter actually exists, if not, create that database.
21+
5. Confirm that once you open your `http://localhost:8000` it loads the home page.
22+
6. Create a new route & controller that will let you upload the transactions CSV file. The UI is not important, so you don't even need any CSS. If you want you can use `HomeController` and simply add a new method and route for it or create a new controller entirely.
23+
7. Your controller should accept the uploaded file, read it line by line & save the data into the **transactions** table. You can download the sample transactions file to upload [here](./transactions_sample.csv)
24+
* Create the **transactions** table with appropriate columns to store the data
25+
* Create a model within the **Models** directory to actually process the file & save data into the database
26+
* First column is the date of the transaction
27+
* Second column is the check # which is optional & is not always provided
28+
* The third column is transaction description
29+
* The fourth column is the amount (negative number indicates it's an expense, positive number indicates it's an income), it's up to you how you want to store it
30+
8. The view file is provided for you under `views/transactions.php` you just need to render this from your controller & pass down the necessary data to display transactions.
31+
* The date of the transaction should be in this format "Jan 4, 2021"
32+
* Show income amounts in green color & show expense amounts in red
33+
9. Submit the PR with your changes, I will review & provide feedback, if you get stuck or have any questions let me know.
34+
10. **Bonus:** Allow multiple file uploads so that more than one CSV file can be uploaded at the same time.

Test.php

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
4+
use PHPUnit\Framework\TestCase;
5+
6+
class Test extends TestCase
7+
{
8+
9+
}

app/App.php

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace App;
6+
7+
use App\Exceptions\RouteNotFoundException;
8+
9+
class App
10+
{
11+
private static DB $db;
12+
13+
public function __construct(protected Router $router, protected array $request, protected Config $config)
14+
{
15+
static::$db = new DB($config->db ?? []);
16+
}
17+
18+
public static function db(): DB
19+
{
20+
return static::$db;
21+
}
22+
23+
public function run()
24+
{
25+
try {
26+
echo $this->router->resolve($this->request['uri'], strtolower($this->request['method']));
27+
} catch (RouteNotFoundException) {
28+
http_response_code(404);
29+
30+
echo View::make('error/404');
31+
}
32+
}
33+
}

app/Config.php

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace App;
6+
7+
/**
8+
* @property-read ?array $db
9+
*/
10+
class Config
11+
{
12+
protected array $config = [];
13+
14+
public function __construct(array $env)
15+
{
16+
$this->config = [
17+
'db' => [
18+
'host' => $env['DB_HOST'],
19+
'user' => $env['DB_USER'],
20+
'pass' => $env['DB_PASS'],
21+
'database' => $env['DB_DATABASE'],
22+
'driver' => $env['DB_DRIVER'] ?? 'mysql',
23+
],
24+
];
25+
}
26+
27+
public function __get(string $name)
28+
{
29+
return $this->config[$name] ?? null;
30+
}
31+
}

app/Controllers/HomeController.php

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace App\Controllers;
6+
7+
use App\View;
8+
9+
class HomeController
10+
{
11+
public function index(): View
12+
{
13+
return View::make('index');
14+
}
15+
}

app/DB.php

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace App;
6+
7+
use PDO;
8+
9+
/**
10+
* @mixin PDO
11+
*/
12+
class DB
13+
{
14+
private PDO $pdo;
15+
16+
public function __construct(array $config)
17+
{
18+
$defaultOptions = [
19+
PDO::ATTR_EMULATE_PREPARES => false,
20+
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
21+
];
22+
23+
try {
24+
$this->pdo = new PDO(
25+
$config['driver'] . ':host=' . $config['host'] . ';dbname=' . $config['database'],
26+
$config['user'],
27+
$config['pass'],
28+
$config['options'] ?? $defaultOptions
29+
);
30+
} catch (\PDOException $e) {
31+
throw new \PDOException($e->getMessage(), (int) $e->getCode());
32+
}
33+
}
34+
35+
public function __call(string $name, array $arguments)
36+
{
37+
return call_user_func_array([$this->pdo, $name], $arguments);
38+
}
39+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace App\Exceptions;
6+
7+
class RouteNotFoundException extends \Exception
8+
{
9+
protected $message = '404 Not Found';
10+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace App\Exceptions;
6+
7+
class ViewNotFoundException extends \Exception
8+
{
9+
protected $message = 'View not found';
10+
}

app/Model.php

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace App;
6+
7+
abstract class Model
8+
{
9+
protected DB $db;
10+
11+
public function __construct()
12+
{
13+
$this->db = App::db();
14+
}
15+
}

app/Router.php

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App;
6+
7+
use App\Exceptions\RouteNotFoundException;
8+
9+
class Router
10+
{
11+
private array $routes;
12+
13+
public function register(string $requestMethod, string $route, callable|array $action): self
14+
{
15+
$this->routes[$requestMethod][$route] = $action;
16+
17+
return $this;
18+
}
19+
20+
public function get(string $route, callable|array $action): self
21+
{
22+
return $this->register('get', $route, $action);
23+
}
24+
25+
public function post(string $route, callable|array $action): self
26+
{
27+
return $this->register('post', $route, $action);
28+
}
29+
30+
public function routes(): array
31+
{
32+
return $this->routes;
33+
}
34+
35+
public function resolve(string $requestUri, string $requestMethod)
36+
{
37+
$route = explode('?', $requestUri)[0];
38+
$action = $this->routes[$requestMethod][$route] ?? null;
39+
40+
if (! $action) {
41+
throw new RouteNotFoundException();
42+
}
43+
44+
if (is_callable($action)) {
45+
return call_user_func($action);
46+
}
47+
48+
if (is_array($action)) {
49+
[$class, $method] = $action;
50+
51+
if (class_exists($class)) {
52+
$class = new $class();
53+
54+
if (method_exists($class, $method)) {
55+
return call_user_func_array([$class, $method], []);
56+
}
57+
}
58+
}
59+
60+
throw new RouteNotFoundException();
61+
}
62+
}

app/View.php

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App;
6+
7+
use App\Exceptions\ViewNotFoundException;
8+
9+
class View
10+
{
11+
public function __construct(
12+
protected string $view,
13+
protected array $params = []
14+
) {
15+
}
16+
17+
public static function make(string $view, array $params = []): static
18+
{
19+
return new static($view, $params);
20+
}
21+
22+
public function render(): string
23+
{
24+
$viewPath = VIEW_PATH . '/' . $this->view . '.php';
25+
26+
if (! file_exists($viewPath)) {
27+
throw new ViewNotFoundException();
28+
}
29+
30+
foreach($this->params as $key => $value) {
31+
$$key = $value;
32+
}
33+
34+
ob_start();
35+
36+
include $viewPath;
37+
38+
return (string) ob_get_clean();
39+
}
40+
41+
public function __toString(): string
42+
{
43+
return $this->render();
44+
}
45+
46+
public function __get(string $name)
47+
{
48+
return $this->params[$name] ?? null;
49+
}
50+
}

composer.json

+12-6
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
{
2-
"autoload": {
3-
"psr-4": {"App\\": "src/App"}
4-
},
5-
"require-dev": {
6-
"phpunit/phpunit": "9.5"
7-
}
2+
"require": {
3+
"ext-pdo": "*",
4+
"vlucas/phpdotenv": "^5.3"
5+
},
6+
"autoload": {
7+
"psr-4": {
8+
"App\\": "app/"
9+
}
10+
},
11+
"config": {
12+
"optimize-autoloader": true
13+
}
814
}

0 commit comments

Comments
 (0)