diff --git a/.dockerignore b/.dockerignore
index 56b89f7..fea7610 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -2,6 +2,8 @@
/public/build/images/glyphicons-*
*.Dockerfile
+docker-compose.*
+Makefile
###> symfony/framework-bundle ###
/.env.local
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..86cc40d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
+.PHONY: all rr fpm builtin apache
+
+%.Dockerfile:
+ docker build -f $@ -t symfony-demo:$(basename $@) .
+
+rr.Dockerfile: base.Dockerfile
+fpm.Dockerfile: base.Dockerfile
+builtin.Dockerfile: base.Dockerfile
+apache.Dockerfile: base.Dockerfile
+
+rr: rr.Dockerfile
+fpm: fpm.Dockerfile
+builtin: builtin.Dockerfile
+apache: apache.Dockerfile
+
+all: rr fpm builtin
diff --git a/apache.Dockerfile b/apache.Dockerfile
new file mode 100644
index 0000000..51b92cf
--- /dev/null
+++ b/apache.Dockerfile
@@ -0,0 +1,24 @@
+FROM php:7.4-apache
+
+RUN apt-get update && apt-get -y install libicu-dev libzip-dev && \
+ docker-php-ext-install bcmath intl opcache zip sockets;
+
+ENV APP_ENV=prod
+ENV DATABASE_URL="sqlite:////var/db/app.db"
+ENV MAILER_DSN="smtp://localhost:25"
+ENV PATH=$PATH:/var/www/bin
+
+WORKDIR /var/www
+
+COPY --from=symfony-demo:base /var/www .
+COPY --from=symfony-demo:base /var/db /var/db
+
+# Timezone
+RUN ln -snf /usr/share/zoneinfo/Europe/Warsaw /etc/localtime && \
+ echo "date.timezone = Europe/Warsaw" >> /usr/local/etc/php/conf.d/datetime.ini;
+
+RUN a2enmod rewrite
+COPY ./docker/apache.conf /etc/apache2/sites-available/000-default.conf
+RUN chown -R www-data:www-data /var/www
+
+CMD ["./bin/docker-init.sh", "apache2-foreground"]
diff --git a/base.Dockerfile b/base.Dockerfile
new file mode 100644
index 0000000..510b196
--- /dev/null
+++ b/base.Dockerfile
@@ -0,0 +1,27 @@
+FROM node:latest as assets
+WORKDIR /var/www
+
+COPY ./assets/ ./assets/
+COPY ./package.json webpack.config.js yarn.lock ./
+RUN yarn install && yarn run build
+
+FROM php:7.4-alpine
+
+ENV APP_ENV=prod
+ENV DATABASE_URL="sqlite:////var/db/app.db"
+ENV MAILER_DSN="smtp://localhost:25"
+
+WORKDIR /var/www
+
+COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
+COPY . .
+COPY --from=assets /var/www/public public
+
+RUN composer install --prefer-dist --no-progress --no-interaction --ignore-platform-reqs && \
+ composer dump-autoload --optimize && \
+ php bin/console cache:warmup
+
+RUN mkdir /var/db && \
+ ./bin/console doctrine:schema:create && \
+ ./bin/console doctrine:schema:update --force && \
+ yes | APP_ENV=dev ./bin/console doctrine:fixtures:load
diff --git a/builtin.Dockerfile b/builtin.Dockerfile
index 33ebfe8..71c277c 100644
--- a/builtin.Dockerfile
+++ b/builtin.Dockerfile
@@ -1,11 +1,9 @@
-FROM node:latest as assets
-WORKDIR /var/www
-
-COPY . .
-RUN yarn install && yarn run build
-
FROM php:7.4-alpine
+RUN apk add --no-cache autoconf openssl-dev g++ make pcre-dev icu-dev zlib-dev libzip-dev git && \
+ docker-php-ext-install bcmath intl opcache zip sockets && \
+ apk del --purge autoconf g++ make;
+
ENV APP_ENV=prod
ENV DATABASE_URL="sqlite:////var/db/app.db"
ENV MAILER_DSN="smtp://localhost:25"
@@ -13,24 +11,8 @@ ENV PATH=$PATH:/var/www/bin
WORKDIR /var/www
-RUN mkdir /var/db
-
-RUN apk add --no-cache autoconf openssl-dev g++ make pcre-dev icu-dev zlib-dev libzip-dev git && \
- docker-php-ext-install bcmath intl opcache zip sockets && \
- apk del --purge autoconf g++ make;
-
-COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
-COPY . .
-COPY --from=assets /var/www/public public
-
-RUN composer install --prefer-dist --no-progress --no-interaction && \
- composer dump-autoload --optimize && \
- composer check-platform-reqs && \
- php bin/console cache:warmup
-
-RUN ./bin/console doctrine:schema:create && \
- ./bin/console doctrine:schema:update --force && \
- yes | APP_ENV=dev ./bin/console doctrine:fixtures:load
+COPY --from=symfony-demo:base /var/www .
+COPY --from=symfony-demo:base /var/db /var/db
# Timezone
RUN ln -snf /usr/share/zoneinfo/Europe/Warsaw /etc/localtime && \
diff --git a/docker-compose.locust.yml b/docker-compose.locust.yml
index 917b11b..eba8fae 100644
--- a/docker-compose.locust.yml
+++ b/docker-compose.locust.yml
@@ -6,11 +6,42 @@ services:
ports:
- "8080:8089"
volumes:
- - ./locust:/mnt/locust
+ - ./:/mnt/locust
command: -f /mnt/locust/locustfile.py --master -H http://master:8089
worker:
image: locustio/locust
volumes:
- - ./locust:/mnt/locust
+ - ./:/mnt/locust
command: -f /mnt/locust/locustfile.py --worker --master-host master
+ depends_on:
+ - master
+
+ frontend-rr:
+ image: symfony-demo:rr
+ ports:
+ - 8081:8080
+
+ frontend-builtin:
+ image: symfony-demo:builtin
+ ports:
+ - 8082:8080
+
+ frontend-fpm:
+ image: nginx:latest
+ volumes:
+ - ./public:/var/www/public
+ - ./docker/symfony-demo.conf:/etc/nginx/conf.d/default.conf
+ - ./docker/upstream.conf:/etc/nginx/conf.d/upstream.conf
+ ports:
+ - 8083:8080
+ depends_on:
+ - fpm
+
+ fpm:
+ image: symfony-demo:fpm
+
+ frontend-apache:
+ image: symfony-demo:apache
+ ports:
+ - 8084:80
diff --git a/docker/apache.conf b/docker/apache.conf
new file mode 100644
index 0000000..4526c98
--- /dev/null
+++ b/docker/apache.conf
@@ -0,0 +1,10 @@
+
+ ServerAdmin webmaster@localhost
+ DocumentRoot /var/www/public
+
+
+ Options Indexes FollowSymLinks
+ AllowOverride All
+ Require all granted
+
+
diff --git a/docker/php-fpm.conf b/docker/php-fpm.conf
new file mode 100644
index 0000000..b85fea0
--- /dev/null
+++ b/docker/php-fpm.conf
@@ -0,0 +1,8 @@
+[www]
+listen = 0.0.0.0:9000
+
+pm = dynamic
+pm.max_children = 32
+pm.start_servers = 8
+pm.min_spare_servers = 4
+pm.max_spare_servers = 8
diff --git a/docker/symfony-demo.conf b/docker/symfony-demo.conf
new file mode 100644
index 0000000..819ce96
--- /dev/null
+++ b/docker/symfony-demo.conf
@@ -0,0 +1,27 @@
+server {
+ listen 8080;
+ root /var/www/public;
+
+ location / {
+ try_files $uri /index.php$is_args$args;
+ }
+
+ location ~ ^/.+\.php(/|$) {
+ fastcgi_buffer_size 256k;
+ fastcgi_buffers 64 256k;
+ fastcgi_pass php-upstream;
+ fastcgi_split_path_info ^(.+\.php)(/.*)$;
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
+ fastcgi_param DOCUMENT_ROOT $realpath_root;
+ internal;
+ }
+
+ location ~ \.php$ {
+ return 404;
+ }
+
+ error_log /var/log/nginx/error.log;
+ access_log /var/log/nginx/access.log;
+}
+
diff --git a/docker/upstream.conf b/docker/upstream.conf
new file mode 100644
index 0000000..f79b448
--- /dev/null
+++ b/docker/upstream.conf
@@ -0,0 +1 @@
+upstream php-upstream { server fpm:9000; }
diff --git a/fpm.Dockerfile b/fpm.Dockerfile
index 5c99b9a..682b57c 100644
--- a/fpm.Dockerfile
+++ b/fpm.Dockerfile
@@ -1,25 +1,23 @@
FROM php:7.4-fpm-alpine
-ENV APP_ENV=prod
-ENV DATABASE_URL="sqlite:////var/db/app.db"
-ENV PATH=$PATH:/usr/src/app/bin
-
RUN apk add --no-cache autoconf openssl-dev g++ make pcre-dev icu-dev zlib-dev libzip-dev git && \
docker-php-ext-install bcmath intl opcache zip sockets && \
apk del --purge autoconf g++ make;
-COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
-COPY . .
+ENV APP_ENV=prod
+ENV DATABASE_URL="sqlite:////var/db/app.db"
+ENV MAILER_DSN="smtp://localhost:25"
+ENV PATH=$PATH:/var/www/bin
+
+WORKDIR /var/www
-RUN composer install --no-dev --no-scripts --no-plugins --prefer-dist --no-progress --no-interaction && \
- composer dump-autoload --optimize && \
- composer check-platform-reqs && \
- php bin/console cache:warmup
+COPY --from=symfony-demo:base /var/www .
+COPY --from=symfony-demo:base /var/db /var/db
# Timezone
RUN ln -snf /usr/share/zoneinfo/Europe/Warsaw /etc/localtime && \
echo "date.timezone = Europe/Warsaw" >> /usr/local/etc/php/conf.d/datetime.ini;
-WORKDIR /var/www
+ADD ./docker/php-fpm.conf /usr/local/etc/php-fpm.d/symfony.conf
CMD ["./bin/docker-init.sh", "php-fpm"]
diff --git a/locustfile.py b/locustfile.py
new file mode 100644
index 0000000..65c2017
--- /dev/null
+++ b/locustfile.py
@@ -0,0 +1,43 @@
+import time
+from locust import HttpUser, task, between
+from random import randint, choice, seed
+
+seed(3721)
+
+class BlogUser(HttpUser):
+ wait_time = between(1, 2.5)
+
+ def on_start(self):
+ self.max_pages = 3
+ self.tags = [ tag['name'] for tag in self.client.get("/en/api/tags").json() ]
+ self.queries = ["Lorem ipsum", "vitae velit", "Ubi est", "dolor"]
+
+ @task(5)
+ def browse_tag(self):
+ tag = choice(self.tags)
+ self.client.get(f"/en/blog?tag={tag}")
+
+ posts = self.client.get(f"/en/api/posts?tag={tag}").json()
+ self.browse_posts(posts, 3)
+
+ @task(10)
+ def browse_task(self):
+ page = randint(1, self.max_pages)
+
+ self.client.get(f"/en/blog/page/{page}")
+ posts = self.client.get(f"/en/api/posts?page={page}").json()
+ self.browse_posts(posts, 4)
+
+ @task(2)
+ def search_task(self):
+ query = choice(self.queries)
+ posts = self.client.get(f"/en/api/search?q={query}").json()
+ self.browse_posts(posts, 2)
+
+ def browse_posts(self, posts, count):
+ if len(posts) == 0:
+ return
+
+ for _ in range(count):
+ post = choice(posts)
+ self.client.get(post['url'])
diff --git a/rr.Dockerfile b/rr.Dockerfile
index cf6bd1b..df03846 100644
--- a/rr.Dockerfile
+++ b/rr.Dockerfile
@@ -1,11 +1,9 @@
-FROM node:latest as assets
-WORKDIR /var/www
-
-COPY . .
-RUN yarn install && yarn run build
-
FROM php:7.4-alpine
+RUN apk add --no-cache autoconf openssl-dev g++ make pcre-dev icu-dev zlib-dev libzip-dev git && \
+ docker-php-ext-install bcmath intl opcache zip sockets && \
+ apk del --purge autoconf g++ make;
+
ENV APP_ENV=prod
ENV DATABASE_URL="sqlite:////var/db/app.db"
ENV MAILER_DSN="smtp://localhost:25"
@@ -13,29 +11,14 @@ ENV PATH=$PATH:/var/www/bin
WORKDIR /var/www
-RUN mkdir /var/db
-
-RUN apk add --no-cache autoconf openssl-dev g++ make pcre-dev icu-dev zlib-dev libzip-dev git && \
- docker-php-ext-install bcmath intl opcache zip sockets && \
- apk del --purge autoconf g++ make;
-
-COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
-COPY . .
-COPY --from=assets /var/www/public public
-
-RUN composer install --prefer-dist --no-progress --no-interaction && \
- composer dump-autoload --optimize && \
- composer check-platform-reqs && \
- php bin/console cache:warmup
-
-RUN ./bin/console doctrine:schema:create && \
- ./bin/console doctrine:schema:update --force && \
- yes | APP_ENV=dev ./bin/console doctrine:fixtures:load
+COPY --from=symfony-demo:base /var/www .
+COPY --from=symfony-demo:base /var/db /var/db
# Timezone
RUN ln -snf /usr/share/zoneinfo/Europe/Warsaw /etc/localtime && \
echo "date.timezone = Europe/Warsaw" >> /usr/local/etc/php/conf.d/datetime.ini;
+# Get rr binary
RUN ./vendor/bin/rr get-binary --location /usr/local/bin
EXPOSE 8080
diff --git a/src/Controller/ApiController.php b/src/Controller/ApiController.php
new file mode 100644
index 0000000..2efbfc8
--- /dev/null
+++ b/src/Controller/ApiController.php
@@ -0,0 +1,107 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace App\Controller;
+
+use App\Entity\Comment;
+use App\Entity\Post;
+use App\Event\CommentCreatedEvent;
+use App\Form\CommentType;
+use App\Repository\PostRepository;
+use App\Repository\TagRepository;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
+use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing\Annotation\Route;
+
+/**
+ * Controller managing API.
+ *
+ * @Route("/api")
+ *
+ * @author Ryan Weaver
+ * @author Javier Eguiluz
+ */
+class ApiController extends AbstractController
+{
+ /**
+ * @Route("/posts", defaults={"_format"="json"}, methods="GET", name="api_index")
+ */
+ public function index(Request $request, PostRepository $posts, TagRepository $tags): Response
+ {
+ $tag = null;
+
+ if ($request->query->has('tag')) {
+ /** @var \App\Entity\Tag $tag */
+ $tag = $tags->findOneBy(['name' => $request->query->get('tag')]);
+ }
+
+ $latestPosts = $posts->findLatest($request->query->get('page', 1), $tag);
+
+ $results = [];
+ foreach ($latestPosts->getResults() as $post) {
+ $results[] = $this->postToJson($post);
+ }
+
+ return $this->json($results);
+ }
+
+ /**
+ * @Route("/tags", defaults={"page": "1", "_format"="json"}, methods="GET", name="api_tags")
+ */
+ public function tags(TagRepository $tags): Response
+ {
+ /** @var \App\Entity\Tag[] $tags */
+ $tags = $tags->findAll();
+
+ $results = [];
+ foreach ($tags as $tag) {
+ $results[] = [
+ 'name' => $tag->getName(),
+ ];
+ }
+
+ return $this->json($results);
+ }
+
+ /**
+ * @Route("/search", defaults={"_format": "json"}, methods="GET", name="api_search")
+ */
+ public function search(Request $request, PostRepository $posts): Response
+ {
+ $query = $request->query->get('q', '');
+ $limit = $request->query->get('l', 10);
+
+ $foundPosts = $posts->findBySearchQuery($query, $limit);
+
+ $results = [];
+ foreach ($foundPosts as $post) {
+ $results[] = $this->postToJson($post);
+ }
+
+ return $this->json($results);
+ }
+
+ private function postToJson(Post $post): array
+ {
+ return [
+ 'title' => htmlspecialchars($post->getTitle(), ENT_COMPAT | ENT_HTML5),
+ 'date' => $post->getPublishedAt()->format('M d, Y'),
+ 'author' => htmlspecialchars($post->getAuthor()->getFullName(), ENT_COMPAT | ENT_HTML5),
+ 'summary' => htmlspecialchars($post->getSummary(), ENT_COMPAT | ENT_HTML5),
+ 'url' => $this->generateUrl('blog_post', ['slug' => $post->getSlug()]),
+ ];
+ }
+}