-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit: Add Docker setup and PHP application files
- Loading branch information
0 parents
commit c3c0734
Showing
14 changed files
with
593 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Use an official PHP image as the base image | ||
FROM php:8.0-apache | ||
|
||
# Install necessary PHP extensions and other dependencies | ||
RUN docker-php-ext-install pdo pdo_mysql | ||
|
||
# Copy application code to the working directory | ||
COPY . /var/www/html | ||
|
||
# Set the working directory | ||
WORKDIR /var/www/html | ||
|
||
# Expose port 80 | ||
EXPOSE 80 | ||
|
||
# Start the Apache server | ||
CMD ["apache2-foreground"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
# PHP Developer Test | ||
|
||
Hello and thanks for taking the time to try this out. | ||
|
||
The goal of this test is to assert (to some degree) your coding and architectural skills. You're given a simple problem so you can focus on showcasing development techniques. We encourage you to overengineer the solution to show off what you can do - assume you're building a production-ready application that other developers will need to work on and add to over time. | ||
|
||
You're **allowed and encouraged** to use third party libraries, as long as you put them together yourself **without relying on a framework or microframework** to do it for you. An effective developer knows what to build and what to reuse, but also how his/her tools work. Be prepared to answer some questions about those libraries, like why you chose them and what other alternatives you're familiar with. | ||
|
||
As this is a code review process, please avoid adding generated code to the project. This makes our jobs as reviewers more difficult, as we can't review code you didn't write. This means avoiding libraries like _Propel ORM_, which generates thousands of lines of code in stub files. | ||
|
||
|
||
## Prerequsites | ||
|
||
We use [Docker](https://www.docker.com/products/docker) to administer this test. This ensures that we get an identical result to you when we test your application out, and it also matches our internal development workflows. If you don't have it already, you'll need Docker installed on your machine. **The application MUST run in the Docker containers** - if it doesn't we cannot accept your submission. You **MAY** edit the containers or add additional ones if you like (or completely re-do everything), but this **MUST** be clearly documented. | ||
|
||
We have provided some containers to help build your application in PHP with a variety of persistence layers available to use. (you may start from scratch if you like) | ||
|
||
### Technology | ||
|
||
- Valid PHP 7.1 or newer | ||
- Persist data to either Postgres, Mysql (add yourself), Redis, or MongoDB (in the provided containers). | ||
- Postgres connection details: | ||
- host: `postgres` | ||
- port: `5432` | ||
- dbname: `hellofresh` | ||
- username: `hellofresh` | ||
- password: `hellofresh` | ||
- Redis connection details: | ||
- host: `redis` | ||
- port: `6379` | ||
- MongoDB connection details: | ||
- host: `mongodb` | ||
- port: `27017` | ||
- Use the provided `docker-compose.yml` file in the root of this repository. You are free to add more containers to this if you like. | ||
|
||
## Instructions | ||
|
||
1. Create a Git Repository and add these files | ||
- Run `docker-compose up -d` to start the development environment. | ||
- Visit `http://localhost` to see the contents of the web container and develop your application. | ||
- Add all code changes to the git repository | ||
- Zip all completed files (with the git repository files) and email back to us. | ||
|
||
## Requirements | ||
|
||
We'd like you to build a simple Recipes API. The API **MUST** conform to REST practices and **MUST** provide the following functionality: | ||
|
||
- List, create, read, update, and delete Recipes | ||
- Search recipes | ||
- Rate recipes | ||
|
||
### Endpoints | ||
|
||
Your application **MUST** conform to the following endpoint structure and return the HTTP status codes appropriate to each operation. Endpoints specified as protected below **SHOULD** require authentication to view. The method of authentication is up to you. | ||
|
||
##### Recipes | ||
|
||
| Name | Method | URL | Protected | | ||
| --- | --- | --- | --- | | ||
| List | `GET` | `/recipes` | ✘ | | ||
| Create | `POST` | `/recipes` | ✓ | | ||
| Get | `GET` | `/recipes/{id}` | ✘ | | ||
| Update | `PUT/PATCH` | `/recipes/{id}` | ✓ | | ||
| Delete | `DELETE` | `/recipes/{id}` | ✓ | | ||
| Rate | `POST` | `/recipes/{id}/rating` | ✘ | | ||
|
||
An endpoint for recipe search functionality **MUST** also be implemented. The HTTP method and endpoint for this **MUST** be clearly documented. | ||
|
||
### Schema | ||
|
||
- **Recipe** | ||
- Unique ID | ||
- Name | ||
- Prep time | ||
- Difficulty (1-3) | ||
- Vegetarian (boolean) | ||
|
||
Additionally, recipes can be rated many times from 1-5 and a rating is never overwritten. | ||
|
||
If you need a more visual idea of how the data should be represented, [take a look at one of our recipe cards](https://ddw4dkk7s1lkt.cloudfront.net/card/hdp-chicken-with-farro-75b306ff.pdf?t=20160927003916). | ||
|
||
## Evaluation criteria | ||
|
||
These are some aspects we pay particular attention to: | ||
|
||
- You **MUST** use packages, but you **MUST NOT** use a web-app framework or microframework. That is, you can use [symfony/dependency-injection](https://packagist.org/packages/symfony/dependency-injection) but not [symfony/symfony](https://packagist.org/packages/symfony/symfony). | ||
- Your application **MUST** run within the containers. Please provide short setup instructions. | ||
- The API **MUST** return valid JSON and **MUST** follow the endpoints set out above. | ||
- You **MUST** write testable code and demonstrate unit testing it (for clarity, PHPUnit is not considered a framework as per the first point above. We encourage you to use PHPUnit or any other kind of **testing** framework). | ||
- You **SHOULD** pay attention to best security practices. | ||
- You **SHOULD** follow SOLID principles where appropriate. | ||
- You do **NOT** have to build a UI for this API. | ||
|
||
The following earn you bonus points: | ||
|
||
- Your answers during code review | ||
- An informative, detailed description in the PR | ||
- Setup with a one liner or a script | ||
- Content negotiation | ||
- Pagination | ||
- Using any kind of Database Access Abstraction | ||
- Other types of testing - e.g. integration tests | ||
- Following the industry standard style guide for the language you choose to use - `PSR-2` etc. | ||
- A git history (even if brief) with clear, concise commit messages. | ||
|
||
--- | ||
|
||
Good luck! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
version: '3.7' | ||
|
||
services: | ||
web: | ||
build: | ||
context: . | ||
dockerfile: Dockerfile | ||
ports: | ||
- "8080:80" | ||
volumes: | ||
- .:/var/www/html | ||
depends_on: | ||
- db | ||
|
||
db: | ||
image: mysql:5.7 | ||
environment: | ||
MYSQL_ROOT_PASSWORD: root | ||
MYSQL_DATABASE: recipe_db | ||
MYSQL_USER: user | ||
MYSQL_PASSWORD: password | ||
ports: | ||
- "3306:3306" | ||
volumes: | ||
- db_data:/var/lib/mysql | ||
|
||
volumes: | ||
db_data: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
server { | ||
listen 80; | ||
|
||
root /server/http/web; | ||
index index.html index.htm index.php; | ||
|
||
access_log off; | ||
error_log /var/log/nginx/error.log error; | ||
|
||
charset utf-8; | ||
|
||
location / { | ||
try_files $uri $uri/ /index.php?$query_string; | ||
} | ||
|
||
location = /favicon.ico { log_not_found off; access_log off; } | ||
location = /robots.txt { access_log off; log_not_found off; } | ||
|
||
sendfile off; | ||
|
||
client_max_body_size 100m; | ||
|
||
location ~ \.php$ { | ||
fastcgi_split_path_info ^(.+\.php)(/.+)$; | ||
fastcgi_pass unix:/run/php/php7.1-fpm.sock; | ||
fastcgi_index index.php; | ||
include fastcgi_params; | ||
include fastcgi.conf; | ||
fastcgi_intercept_errors off; | ||
fastcgi_buffer_size 16k; | ||
fastcgi_buffers 4 16k; | ||
} | ||
|
||
# Deny .htaccess file access | ||
location ~ /\.ht { | ||
deny all; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Create Recipe</title> | ||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous"> | ||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script> | ||
</head> | ||
<body> | ||
<div class="container"> | ||
<h2 class="text-center">Create Recipe</h2> | ||
<form action="create_action.php" method="POST"> | ||
<div class="mb-3"> | ||
<label for="name" class="form-label">Recipe Name</label> | ||
<input type="text" class="form-control" id="name" name="name" required> | ||
</div> | ||
<div class="mb-3"> | ||
<label for="prep_time" class="form-label">Preparation Time</label> | ||
<input type="text" class="form-control" id="prep_time" name="prep_time" required> | ||
</div> | ||
<div class="mb-3"> | ||
<label for="difficulty" class="form-label">Difficulty</label> | ||
<input type="text" class="form-control" id="difficulty" name="difficulty" required> | ||
</div> | ||
<div class="mb-3"> | ||
<label for="vegetarian" class="form-label">Vegetarian</label> | ||
<select class="form-control" id="vegetarian" name="vegetarian" required> | ||
<option value="1">Yes</option> | ||
<option value="0">No</option> | ||
</select> | ||
</div> | ||
<button type="submit" class="btn btn-primary">Create</button> | ||
</form> | ||
</div> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<?php | ||
include 'db.php'; | ||
|
||
$name = $_POST['name']; | ||
$prep_time = $_POST['prep_time']; | ||
$difficulty = $_POST['difficulty']; | ||
$vegetarian = $_POST['vegetarian']; | ||
|
||
$sql = "INSERT INTO recipe (name, prep_time, difficulty, vegetarian) VALUES (?, ?, ?, ?)"; | ||
$stmt = $conn->prepare($sql); | ||
$stmt->bind_param("sssi", $name, $prep_time, $difficulty, $vegetarian); | ||
|
||
if ($stmt->execute()) { | ||
header("Location: index.php"); | ||
} else { | ||
echo "Error: " . $sql . "<br>" . $conn->error; | ||
} | ||
|
||
$stmt->close(); | ||
$conn->close(); | ||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<?php | ||
$servername = "db"; | ||
$username = "user"; | ||
$password = "password"; | ||
$dbname = "recipe_db"; | ||
|
||
// Create connection | ||
$conn = new mysqli($servername, $username, $password, $dbname); | ||
|
||
// Check connection | ||
if ($conn->connect_error) { | ||
die("Connection failed: " . $conn->connect_error); | ||
} | ||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
<?php | ||
include 'db.php'; | ||
|
||
$id = $_POST['id']; | ||
|
||
$sql = "DELETE FROM recipe WHERE id = ?"; | ||
$stmt = $conn->prepare($sql); | ||
$stmt->bind_param("i", $id); | ||
|
||
if ($stmt->execute()) { | ||
header("Location: index.php"); | ||
} else { | ||
echo "Error: " . $sql . "<br>" . $conn->error; | ||
} | ||
|
||
$stmt->close(); | ||
$conn->close(); | ||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<title>Recipe CRUD APP</title> | ||
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous"> | ||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script> | ||
</head> | ||
<body> | ||
<div class="container"> | ||
<h2 class="text-center">Recipe CRUD APP</h2> | ||
|
||
<form method="GET" action="index.php" class="d-flex mb-4"> | ||
<input class="form-control me-2" type="search" name="search" placeholder="Search by name" value="<?php echo isset($_GET['search']) ? $_GET['search'] : ''; ?>"> | ||
<button class="btn btn-outline-success" type="submit">Search</button> | ||
</form> | ||
|
||
<a href="create.php" class="btn btn-primary mb-3">Create</a> | ||
|
||
<table class="table table-hover"> | ||
<thead> | ||
<tr> | ||
<th scope="col">ID</th> | ||
<th scope="col">Name</th> | ||
<th scope="col">Prep Time</th> | ||
<th scope="col">Difficulty</th> | ||
<th scope="col">Vegetarian</th> | ||
<th scope="col">Actions</th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<?php | ||
include 'db.php'; | ||
|
||
// Search functionality | ||
$search = isset($_GET['search']) ? $_GET['search'] : ''; | ||
|
||
// Pagination functionality | ||
$limit = 5; // Number of entries per page | ||
$page = isset($_GET['page']) ? $_GET['page'] : 1; | ||
$offset = ($page - 1) * $limit; | ||
|
||
// SQL query with search and pagination | ||
$sql = "SELECT * FROM recipe WHERE name LIKE ? LIMIT ?, ?"; | ||
$stmt = $conn->prepare($sql); | ||
$searchParam = '%' . $search . '%'; | ||
$stmt->bind_param('sii', $searchParam, $offset, $limit); | ||
$stmt->execute(); | ||
$result = $stmt->get_result(); | ||
|
||
if ($result->num_rows > 0) { | ||
while ($row = $result->fetch_assoc()) { | ||
?> | ||
<tr> | ||
<th scope="row"><?php echo $row["id"]; ?></th> | ||
<td><?php echo $row["name"]; ?></td> | ||
<td><?php echo $row["prep_time"]; ?></td> | ||
<td><?php echo $row["difficulty"]; ?></td> | ||
<td><?php echo $row["vegetarian"] ? 'Yes' : 'No'; ?></td> | ||
<td> | ||
<a href="read.php?id=<?php echo $row['id']; ?>"><button class="btn btn-info">READ</button></a> | ||
<a href="update.php?id=<?php echo $row['id']; ?>"><button class="btn btn-warning">EDIT</button></a> | ||
<form action="delete.php" method="POST" style="display:inline;" onsubmit="return confirm('Are you sure you want to delete this item?');"> | ||
<input type="hidden" name="id" value="<?php echo $row['id']; ?>"> | ||
<button type="submit" class="btn btn-danger">DELETE</button> | ||
</form> | ||
<a href="rate.php?id=<?php echo $row['id']; ?>"><button class="btn btn-success">RATE</button></a> | ||
</td> | ||
</tr> | ||
<?php | ||
} | ||
} else { | ||
echo "<tr><td colspan='6' class='text-center'>No records found</td></tr>"; | ||
} | ||
$stmt->close(); | ||
|
||
// Total records for pagination | ||
$sql_total = "SELECT COUNT(*) as count FROM recipe WHERE name LIKE ?"; | ||
$stmt_total = $conn->prepare($sql_total); | ||
$stmt_total->bind_param('s', $searchParam); | ||
$stmt_total->execute(); | ||
$result_total = $stmt_total->get_result(); | ||
$total = $result_total->fetch_assoc()['count']; | ||
$stmt_total->close(); | ||
|
||
$conn->close(); | ||
?> | ||
</tbody> | ||
</table> | ||
|
||
<!-- Pagination Links --> | ||
<?php | ||
$total_pages = ceil($total / $limit); | ||
if ($total_pages > 1) { | ||
echo '<nav>'; | ||
echo '<ul class="pagination justify-content-center">'; | ||
for ($i = 1; $i <= $total_pages; $i++) { | ||
echo '<li class="page-item ' . ($i == $page ? 'active' : '') . '"><a class="page-link" href="index.php?page=' . $i . '&search=' . $search . '">' . $i . '</a></li>'; | ||
} | ||
echo '</ul>'; | ||
echo '</nav>'; | ||
} | ||
?> | ||
</div> | ||
</body> | ||
</html> |
Oops, something went wrong.