Using Dompdf on local or hosting Docker instance
Dompdf is a library that renders the page and creates a .pdf file out of it. This is useful if you want to add any specific theming or images to the .pdf. Dompdf relies heavily on mbstring and iconv packages available in PHP. By default these are included in all the PHP Docker images we are using (Wodby or Amazee), because the inherit them from the same base image. Although there are still some issues around this and this document explains an workaround if you ever come in need of using Dompdf together with Docker.
Dockerfile
To first have some insight on the hows and whys, here is briefly how Dockerfiles work. Essentially, they are files which Docker reads and they contain instructions on how to create the container. With Docker4Drupal we are using Wodby's images and one of them is wodby/drupal-php. If you take a look into the Dockerfile of PHP version 7, you will see things like this:
FROM wodby/php:${BASE_IMAGE_TAG}
Tells Docker what is the base image so it inherits instructions from there. That one inherits another one and so on until you reach the very base image.
RUN set -ex; \
\
# Drush
su-exec www-data composer global require drush/drush; \
\
# Drush launcher
wget -O drush.phar \
"https://github.com/drush-ops/drush-launcher/releases/download/${DRUSH_LAUNCHER_VER}/drush.phar"; \
chmod +x drush.phar; \
mv drush.phar /usr/local/bin/drush; \
Tells Docker which commands to run when initialising the container. In this case it's installing drush.
Iconv
Now back to the issue of the .pdf - the iconv package. As seen here there is an issue with the package on one of the base images (docker-library/php). Since this is an issue in the base PHP image, I can only assume that this will be an issue on every PHP related image.
The fix as suggested in this comment is as follows:
RUN apk add --no-cache --repository http://dl-3.alpinelinux.org/alpine/edge/testing gnu-libiconv
ENV LD_PRELOAD /usr/lib/preloadable_libiconv.so php
So we need to add these two extra lines of instructions into our Dockerfile, so they get run upon initialisation of the container. Since we are using docker-compose.yml and images from the official Docker repository, we need to take the image we are using, clone it on our local repository of the project and adjust the Dockerfile. So we take the wodby/drupal-php repository and place it into the docker folder of our project.
PROJECT_ROOT
- docker
- images
-drupal-php
- 7
- Dockerfile
And we update the file with the two new lines so we end up with somethink like this.
ARG BASE_IMAGE_TAG
FROM wodby/php:${BASE_IMAGE_TAG}
ENV DRUSH_LAUNCHER_VER="0.5.1" \
\
PHP_REALPATH_CACHE_TTL="3600" \
PHP_OUTPUT_BUFFERING="16384" \
\
DRUSH_PATCHFILE_URL="https://bitbucket.org/davereid/drush-patchfile.git"
USER root
RUN set -ex; \
\
# Drush
su-exec www-data composer global require drush/drush; \
\
# Drush launcher
wget -O drush.phar \
"https://github.com/drush-ops/drush-launcher/releases/download/${DRUSH_LAUNCHER_VER}/drush.phar"; \
chmod +x drush.phar; \
mv drush.phar /usr/local/bin/drush; \
\
# Drush extensions
su-exec www-data drush @none dl registry_rebuild-7.x; \
su-exec www-data git clone "${DRUSH_PATCHFILE_URL}" /home/www-data/.drush/drush-patchfile; \
\
# Drupal console
curl https://drupalconsole.com/installer -L -o drupal.phar; \
mv drupal.phar /usr/local/bin/drupal; \
chmod +x /usr/local/bin/drupal; \
\
mv /usr/local/bin/actions.mk /usr/local/bin/php.mk; \
# Change overridden target name to avoid warnings.
sed -i 's/git-checkout:/php-git-checkout:/' /usr/local/bin/php.mk; \
\
# Clean up
su-exec www-data composer clear-cache; \
su-exec www-data drush cc drush; \
\
apk add --no-cache --repository http://dl-3.alpinelinux.org/alpine/edge/testing gnu-libiconv;
ENV LD_PRELOAD /usr/lib/preloadable_libiconv.so php
USER www-data
COPY templates /etc/gotpl/
COPY actions /usr/local/bin
COPY init /docker-entrypoint-init.d/
Now what remains is to tell our docker-compose.yml file to take this Dockerfile instead and build the container from it. So our new service should look something like this.
php
# 2. Images without Drupal – wodby/drupal-php:[PHP_VERSION]-[STABILITY_TAG].
# image: wodby/drupal-php:7.1-3.3.1
build:
context: ./images/drupal-php/7
dockerfile: Dockerfile
args:
BASE_IMAGE_TAG: 7.1-3.3.1
environment:
PHP_SENDMAIL_PATH: /usr/sbin/sendmail -t -i -S mailhog:1025
PHP_FPM_CLEAR_ENV: "no"
DB_HOST: mariadb
DB_USER: drupal
DB_PASSWORD: drupal
DB_NAME: drupal
DB_DRIVER: mysql
PHP_XDEBUG: 1
PHP_XDEBUG_DEFAULT_ENABLE: 1
PHP_XDEBUG_REMOTE_CONNECT_BACK: 0
PHP_XDEBUG_REMOTE_HOST: "10.254.254.254"
volumes:
- ../:/var/www/html:delegated # User-guided caching
So instead of the image we are using the build parameter. 'context' tells Docker what is the path to the Dockerfile, 'dockerfile' what is the name of the file and 'args' define the arguments needed to pass. Befoe with the image we have '7.1-3.3.1' which is the version and stability tag. And then the Dockerfile reads that on the first line like so: 'ARG BASE_IMAGE_TAG'. Now because all these images and containers are cached on our machine we need to tell docker-compose.yml to rebuild this specific image, because we have new instructions for it. We do this like so.
docker-compose up -d --build
This solves the iconv issue, but we are not done yet.
Retrieving assets with file_get_contents
The Dompdf or entity_print module need to retrieve all the assets from the page in order to apply them properly on the page so that the .pdf gets rendered nicely. This involves files such as CSS and images. And it does this by using file_get_contents PHP function, which takes an URL and retrieves the contents of that file. And there is again an issue with this.
Take an URL like project.docker.localhost:8000/theme/logo.png. You open that file in the browser and the file opens nicely. Use the PHP function and it returns false. This is because the domain project.docker.localhost is being done using traefik and that only resolves on your machine (host computer). project.docker.localhost resolves to 127.0.0.1 and then Docker opens the correct container and folder for you to view the site. But the PHP function runs inside the PHP container. So it's running inside of Docker, which has it's own network and the domain doesn't even exist. So the domain resolving that happens on your host is not happening anymore inside Docker.
So we need to make a sort of a loopback of the domain back to our host machine where Docker will know what to do with the domain. We do this by editing the /etc/hosts file inside of the PHP container, just like we do on our own host machine.
The best way to do this is by using a specific extra_hosts parameter in docker-compose.yml. This takes an array and on initialisation it puts them into the /etc/hosts file in the container. This part looks something like this.
extra_hosts:
- "project.docker.localhost:172.18.0.1"
Now with this in place, the domain will resolve properly inside the ocntainer and the function will be able to retrieve the assets and generate the .pdf properly. Only thing here to note is the IP. This is infact the IP of the host machine from inside the container. And this might be different on each environment. The easiest way to get this IP is to SSH into the container and run a command.
docker-compose exec php sh
ip route | awk '/^default via /{print $3}
Then just rebuild the containers and Dompdf should nicely generate a .pdf out of the page. Here is also the whole php service written inside of the docker-compose.yml file.
php:
# 1. Images with vanilla Drupal – wodby/drupal:[DRUPAL_VERSION]-[PHP_VERSION]-[STABILITY_TAG].
# image: wodby/drupal:8-7.1-3.3.2
# image: wodby/drupal:8-7.0-3.3.2
# image: wodby/drupal:7-7.1-3.3.2
# image: wodby/drupal:7-7.0-3.3.2
# image: wodby/drupal:7-5.6-3.3.2
# image: wodby/drupal:6-5.6-3.3.2
# image: wodby/drupal:6-5.3-3.3.2
# 2. Images without Drupal – wodby/drupal-php:[PHP_VERSION]-[STABILITY_TAG].
# image: wodby/drupal-php:7.1-3.3.1
# image: wodby/drupal-php:7.0-3.3.1
# image: wodby/drupal-php:5.6-3.3.1
# image: wodby/drupal-php:5.3-3.3.1
# 3. Images without Drupal – wodby/drupal-php:[PHP_VERSION]-[STABILITY_TAG]. Version for development (--enable-debug)
# image: wodby/drupal-php:7.1-dev-3.3.1
# image: wodby/drupal-php:7.0-dev-3.3.1
# image: wodby/drupal-php:5.6-dev-3.3.1
# image: wodby/drupal-php:5.3-dev-3.3.1
build:
context: ./images/drupal-php/7
dockerfile: Dockerfile
args:
BASE_IMAGE_TAG: 7.1-3.3.1
# This is to enable PHP accessing assets that resolve to the local project
# domain. The IP on the right is the IP of the host machine (computer) from
# within the container. It might be different in each case. To check out
# for sure ssh into the container and run
# 'ip route | awk '/^default via /{print $3}'. The returned IP should be
# then used.
extra_hosts:
- "project.docker.localhost:172.18.0.1"
environment:
PHP_SENDMAIL_PATH: /usr/sbin/sendmail -t -i -S mailhog:1025
PHP_FPM_CLEAR_ENV: "no"
DB_HOST: mariadb
DB_USER: drupal
DB_PASSWORD: drupal
DB_NAME: drupal
DB_DRIVER: mysql
PHP_XDEBUG: 1
PHP_XDEBUG_DEFAULT_ENABLE: 1
PHP_XDEBUG_REMOTE_CONNECT_BACK: 0
PHP_XDEBUG_REMOTE_HOST: "10.254.254.254"
# PHP_XDEBUG_PROFILER_OUTPUT_DIR: /mnt/files/xdebug/profiler
# PHP_XDEBUG_TRACE_OUTPUT_DIR: /mnt/files/xdebug/traces
# PHP_BLACKFIRE: 1
volumes:
# - codebase:/var/www/html
## Options for macOS users (https://docker4drupal.readthedocs.io/en/latest/macos)
- ../:/var/www/html:delegated # User-guided caching
# - docker-sync:/var/www/html # Docker-sync
## For Xdebug profiler files
# - files:/mnt/files