diff --git a/defaults/main.yml b/defaults/main.yml
index 4159fb406cd77876041c70d2324ef9fc678edf99..9ff302c03848651015af9e212c30313d402f6409 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -19,6 +19,27 @@ php_config:
     files:
       - path: /etc/php/8.1/apache2/php.ini
         state: absent
+      - name: PHP 8.1 foo configuration
+        path: /etc/php/8.1/fpm/foo.ini
+        state: present
+        conf:
+          PHP:
+            max_input_time: "600"
+            memory_limit: "512M"
+      - name: PHP 8.1 bar configuration
+        path: /etc/php/8.1/fpm/bar.ini
+        state: templated
+        conf:
+          PHP:
+            max_input_time: "600"
+            memory_limit: "512M"
+      - name: PHP 8.1 baz configuration
+        path: /etc/php/8.1/fpm/baz.ini
+        state: present
+        conf:
+          PHP:
+            max_input_time: "600"
+            memory_limit: "512M"
       - name: PHP 8.1 FPM configuration
         path: /etc/php/8.1/fpm/php.ini
         state: edited
@@ -70,6 +91,7 @@ php_versions:
       - php8.2-common
       - php8.2-curl
       - php8.2-gd
+      - php8.2-gmp
       - php8.2-fpm
       - php8.2-imagick
       - php8.2-imap
@@ -85,7 +107,9 @@ php_versions:
       - php8.2-xmlrpc
       - php8.2-xsl
       - php8.2-zip
-  - name: PHP 8.1
+      - php-pear
+      - php-soap
+  - name: PHP 8.1 packages
     version: "8.1"
     state: present
     pkg_absent:
@@ -99,6 +123,7 @@ php_versions:
       - php8.1-common
       - php8.1-curl
       - php8.1-gd
+      - php8.1-gmp
       - php8.1-fpm
       - php8.1-imagick
       - php8.1-imap
@@ -114,6 +139,8 @@ php_versions:
       - php8.1-xmlrpc
       - php8.1-xsl
       - php8.1-zip
+      - php-pear
+      - php-soap
   - name: PHP 8.0 packages
     version: "8.0"
     state: present
@@ -128,6 +155,7 @@ php_versions:
       - php8.0-common
       - php8.0-curl
       - php8.0-gd
+      - php8.0-gmp
       - php8.0-fpm
       - php8.0-imagick
       - php8.0-imap
@@ -143,6 +171,8 @@ php_versions:
       - php8.0-xmlrpc
       - php8.0-xsl
       - php8.0-zip
+      - php-pear
+      - php-soap
   - name: PHP 7.4 packages
     version: "7.4"
     state: present
@@ -157,6 +187,7 @@ php_versions:
       - php7.4-common
       - php7.4-curl
       - php7.4-gd
+      - php7.4-gmp
       - php7.4-geoip
       - php7.4-fpm
       - php7.4-imagick
@@ -174,6 +205,8 @@ php_versions:
       - php7.4-xmlrpc
       - php7.4-xsl
       - php7.4-zip
+      - php-pear
+      - php-soap
   - name: PHP 7.3 packages
     version: "7.3"
     state: absent
diff --git a/tasks/apt.yml b/tasks/apt.yml
index 656eb6a4e2ee814b5241e7a38216cc93a12f28c3..f7b8f7c23559f75153915ee8ddb0b701a9f5d615 100644
--- a/tasks/apt.yml
+++ b/tasks/apt.yml
@@ -1,3 +1,12 @@
+# Copyright 2019-2023 Chris Croome
+#
+# This file is part of the Webarchitects PHP Ansible role.
+#
+# The Webarchitects PHP Ansible role is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+#
+# The Webarchitects PHP Ansible role is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with the Webarchitects PHP Ansible role. If not, see <https://www.gnu.org/licenses/>.
 ---
 - name: Sury PHP apt repo present
   block:
diff --git a/tasks/checks.yml b/tasks/checks.yml
index 8f1a17602341b15d26ebaf791ec7acd245bc0ace..7e60420c024edfd127660c2f07dc9bcc0a7cf661 100644
--- a/tasks/checks.yml
+++ b/tasks/checks.yml
@@ -1,3 +1,12 @@
+# Copyright 2019-2023 Chris Croome
+#
+# This file is part of the Webarchitects PHP Ansible role.
+#
+# The Webarchitects PHP Ansible role is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+#
+# The Webarchitects PHP Ansible role is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with the Webarchitects PHP Ansible role. If not, see <https://www.gnu.org/licenses/>.
 ---
 - name: PHP checks
   block:
diff --git a/tasks/conf.yml b/tasks/conf.yml
index 00ddbb4e12caa3a3be8a59074c7f8565d49b9fa5..2355451355052f69d44233891c94e35f6cd8a0ff 100644
--- a/tasks/conf.yml
+++ b/tasks/conf.yml
@@ -30,12 +30,12 @@
     - name: Debug existing PHP versions directories
       ansible.builtin.debug:
         var: php_conf_dirs_existing
-        verbosity: "{% if ansible_check_mode | bool %}0{% else %}1{% endif %}"
+        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
 
     - name: Debug PHP version configuration directories which should be absent
       ansible.builtin.debug:
         var: php_conf_dirs_absent
-        verbosity: "{% if ansible_check_mode | bool %}0{% else %}1{% endif %}"
+        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
 
     - name: Set a fact for existing PHP version configuration directories which should be absent
       ansible.builtin.set_fact:
@@ -66,12 +66,12 @@
     - name: Debug existing PHP configuration files
       ansible.builtin.debug:
         var: php_conf_files_existing
-        verbosity: "{% if ansible_check_mode | bool %}0{% else %}1{% endif %}"
+        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
 
     - name: Debug PHP configuration files which should be absent
       ansible.builtin.debug:
         var: php_conf_files_absent
-        verbosity: "{% if ansible_check_mode | bool %}0{% else %}1{% endif %}"
+        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
 
     - name: Set a fact for existing PHP configuration files that should be absent
       ansible.builtin.set_fact:
@@ -93,14 +93,105 @@
 
     - name: PHP configuration paths absent
       ansible.builtin.file:
-        path: "{{ php_conf_rm }}"
+        path: "{{ php_rm }}"
         state: absent
+      loop: "{{ php_conf_rm }}"
+      loop_control:
+        loop_var: php_rm
       register: php_conf_removed
       when:
         - php_conf_rm is defined
         - php_conf_rm != []
 
+    - name: Debug PHP configuration directories which should be present
+      ansible.builtin.debug:
+        var: php_conf_dirs_present
+        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
+
+    - name: Set a fact for PHP configuration directories that need to be created
+      ansible.builtin.set_fact:
+        php_conf_dirs_create: "{{ php_conf_dirs_present | ansible.builtin.difference(php_conf_dirs_existing) }}"
+
+    - name: Debug PHP configuration directories that need to be created
+      ansible.builtin.debug:
+        var: php_conf_dirs_create
+        verbosity: "{% if ansible_check_mode | bool %}0{% else %}1{% endif %}"
+
+    - name: PHP configuration directories present
+      ansible.builtin.file:
+        path: "{{ php_dir }}"
+        state: directory
+        mode: 0755
+        owner: root
+        group: root
+      loop: "{{ php_conf_dirs_create }}"
+      loop_control:
+        loop_var: php_dir
+      register: php_conf_dir_created
+      when:
+        - php_conf_dirs_create is defined
+        - php_conf_dirs_create != []
+
+    - name: Debug PHP configuration files set to be edited
+      ansible.builtin.debug:
+        var: php_conf_files_edited
+        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
+
+    - name: Ensure that all files that are set to be edited already exist
+      ansible.builtin.assert:
+        that:
+          - php_conf_files_edited | ansible.builtin.difference(php_conf_files_existing) | length == 0
+        quiet: "{% if ansible_verbosity == 0 %}true{% else %}false{% endif %}"
+        fail_msg: "PHP configurations files that don't exist can't be edited: {{ php_conf_files_edited | ansible.builtin.difference(php_conf_files_existing) }}"
+
+    - name: Debug PHP configuration files set to be present
+      ansible.builtin.debug:
+        var: php_conf_files_present
+        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
+
+    - name: Set a fact for all PHP configuration files that should be edited
+      ansible.builtin.set_fact:
+        php_conf_files_edit: "{{ php_conf_files_edited + php_conf_files_present | ansible.builtin.intersect(php_conf_files_existing) }}"
+
+    - name: Debug all PHP configuration files that should be edited
+      ansible.builtin.debug:
+        var: php_conf_files_edit
+        verbosity: "{% if ansible_check_mode | bool %}0{% else %}1{% endif %}"
+
+    - name: Include PHP configuration file edited tasks
+      ansible.builtin.include_tasks: file_edited.yml
+      loop: "{{ php_conf_files_edit }}"
+      loop_control:
+        loop_var: php_conf_file
+      when:
+        - php_conf_files_edit is defined
+        - php_conf_files_edit != []
+
+    - name: Debug PHP configuration files set to be templated
+      ansible.builtin.debug:
+        var: php_conf_files_templated
+        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
+
+    - name: Set a fact for all PHP configuration files that should be templated
+      ansible.builtin.set_fact:
+        php_conf_files_template: "{{ php_conf_files_templated + php_conf_files_present | ansible.builtin.difference(php_conf_files_existing) }}"
+
+    - name: Debug all PHP configuration files that should be templated
+      ansible.builtin.debug:
+        var: php_conf_files_template
+        verbosity: "{% if ansible_check_mode | bool %}0{% else %}1{% endif %}"
+
+#    - name: Include PHP configuration file templated tasks
+#      ansible.builtin.include_tasks: file_templated.yml
+#      loop: "{{ php_conf_files_template }}"
+#      loop_control:
+#        loop_var: php_conf_file
+#      when:
+#        - php_conf_files_template is defined
+#        - php_conf_files_template != []
+
   tags:
     - php
+    - php_cfg
     - php_conf
 ...
diff --git a/tasks/file_edited.yml b/tasks/file_edited.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e12c9efbd0ad42372266db1b6f342f5f2a71e98e
--- /dev/null
+++ b/tasks/file_edited.yml
@@ -0,0 +1,64 @@
+# Copyright 2019-2023 Chris Croome
+#
+# This file is part of the Webarchitects PHP Ansible role.
+#
+# The Webarchitects PHP Ansible role is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+#
+# The Webarchitects PHP Ansible role is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with the Webarchitects PHP Ansible role. If not, see <https://www.gnu.org/licenses/>.
+---
+- name: PHP configuration file edited
+  block:
+
+    - name: Slurp existing PHP configuration file
+      ansible.builtin.slurp:
+        path: "{{ php_conf_file }}"
+      register: php_conf_file_b64encoded
+
+    - name: Set a fact for the existing PHP configuration file variables
+      ansible.builtin.set_fact:
+        php_conf_file_existing_vars: "{{ php_conf_file_b64encoded['content'] | ansible.builtin.b64decode | community.general.jc('ini') }}"
+
+    - name: Debug the existing PHP configuration file variables
+      ansible.builtin.debug:
+        var: php_conf_file_existing_vars
+        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
+
+    - name: Debug the existing PHP configuration file sections
+      ansible.builtin.debug:
+        var: php_conf_file_existing_vars.keys()
+        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
+
+    - name: Set a fact for the proposed PHP configuration file variables
+      ansible.builtin.set_fact:
+        php_conf_file_proposed_vars: "{{ php_config | ansible.builtin.json_query(php_conf_file_proposed_vars_json_query) }}"
+        verbosity: "{% if ansible_check_mode | bool %}0{% else %}1{% endif %}"
+      vars:
+        php_conf_file_proposed_vars_json_query: "[?state=='present'].files[]|[?path=='{{ php_conf_file }}'].conf|[0]"
+
+    - name: Debug the proposed PHP configuration file variables
+      ansible.builtin.debug:
+        var: php_conf_file_proposed_vars
+        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
+
+    - name: Debug the proposed PHP configuration file sections
+      ansible.builtin.debug:
+        var: php_conf_file_proposed_vars.keys()
+        verbosity: "{% if ansible_check_mode | bool %}1{% else %}2{% endif %}"
+
+    # TODO Use the community.general.dependent lookup to replace the looped include and the loop in the included file...
+    #      https://docs.ansible.com/ansible/latest/collections/community/general/dependent_lookup.html
+
+    - name: Include the file section edited tasks
+      ansible.builtin.include_tasks: file_section_edited.yml
+      loop: "{{ php_conf_file_proposed_vars | dict2items }}"
+      loop_control:
+        loop_var: php_conf_section
+      when: php_conf_file_proposed_vars.keys() | length != 0
+
+  tags:
+    - php
+    - php_cfg
+    - php_conf
+...
diff --git a/tasks/file_section_edited.yml b/tasks/file_section_edited.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cc298ba49caf4b950e30d8b12d39a70648871e69
--- /dev/null
+++ b/tasks/file_section_edited.yml
@@ -0,0 +1,50 @@
+# Copyright 2019-2023 Chris Croome
+#
+# This file is part of the Webarchitects PHP Ansible role.
+#
+# The Webarchitects PHP Ansible role is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+#
+# The Webarchitects PHP Ansible role is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with the Webarchitects PHP Ansible role. If not, see <https://www.gnu.org/licenses/>.
+---
+- name: PHP configuration file section edited
+  block:
+
+    - name: Debug the existing and proposed PHP configuration file variables
+      ansible.builtin.debug:
+        msg:
+          - "File: {{ php_conf_file }}"
+          - "Section: {{ php_conf_section.key }}"
+          - "Existing {{ php_conf_variable_pair.key }}: '{{ php_conf_file_existing_vars | ansible.builtin.json_query(php_conf_variable_json_query) }}'"
+          - "Proposed {{ php_conf_variable_pair.key }}: '{{ php_conf_variable_pair.value }};"
+        verbosity: "{% if ansible_check_mode | bool %}0{% else %}1{% endif %}"
+      when: php_conf_variable_pair.value != php_conf_file_existing_vars | ansible.builtin.json_query(php_conf_variable_json_query)
+      vars:
+        php_conf_variable_json_query: "{{ php_conf_section.key }}.{{ php_conf_variable_pair.key }}"
+      loop: "{{ php_conf_section.value | ansible.builtin.dict2items }}"
+      loop_control:
+        loop_var: php_conf_variable_pair
+
+    - name: Systemd unit file edited
+      community.general.ini_file:
+        path: "{{ php_conf_file }}"
+        section: "{{ php_conf_section.key }}"
+        option: "{{ php_conf_variable_pair.key }}"
+        value: "{{ php_conf_variable_pair.value }}"
+        no_extra_spaces: true
+        mode: 0644
+        owner: root
+        group: root
+      when: php_conf_variable_pair.value != php_conf_file_existing_vars | ansible.builtin.json_query(php_conf_variable_json_query)
+      vars:
+        php_conf_variable_json_query: "{{ php_conf_section.key }}.{{ php_conf_variable_pair.key }}"
+      loop: "{{ php_conf_section.value | ansible.builtin.dict2items }}"
+      loop_control:
+        loop_var: php_conf_variable_pair
+
+  tags:
+    - php
+    - php_cfg
+    - php_conf
+...
diff --git a/tasks/file_templated.yml b/tasks/file_templated.yml
new file mode 100644
index 0000000000000000000000000000000000000000..dc93ab0a891d679a6ff3c4c15898c195483a0bd2
--- /dev/null
+++ b/tasks/file_templated.yml
@@ -0,0 +1,18 @@
+# Copyright 2019-2023 Chris Croome
+#
+# This file is part of the Webarchitects PHP Ansible role.
+#
+# The Webarchitects PHP Ansible role is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+#
+# The Webarchitects PHP Ansible role is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with the Webarchitects PHP Ansible role. If not, see <https://www.gnu.org/licenses/>.
+---
+- name: PHP configuration file templated
+  block:
+
+  tags:
+    - php
+    - php_cfg
+    - php_conf
+...
diff --git a/tasks/main.yml b/tasks/main.yml
index ddb9adb5e4dca3815a3984bb8b21d04c2e7f95a4..aef3e568342c0f462336f8c1a660ec5a0f40fd4d 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -25,6 +25,7 @@
     - name: Include PHP check tasks
       ansible.builtin.include_tasks: checks.yml
       tags:
+        - php_cfg
         - php_conf
 
     - name: Include Sury PHP apt repo tasks
@@ -43,6 +44,7 @@
       ansible.builtin.include_tasks: conf.yml
       when: php_config is defined
       tags:
+        - php_cfg
         - php_conf
 
 # - name: Install and configure PHP
diff --git a/vars/main.yml b/vars/main.yml
index ea9904ba301ad7b3d66d5efa23f5325d77b5e5d3..ee0dd88c4b8019b3d26e615ab237741ace61085e 100644
--- a/vars/main.yml
+++ b/vars/main.yml
@@ -10,12 +10,12 @@
 ---
 # PHP versions absent
 # cat defaults/main.yml | yq -o=json | jp "php_versions[?state=='absent'].version"
-php_ver_absent: "{{ php_versions | ansible.builtin.json_query('[?state==`absent`].version') }}"
-php_ver_absent_regex: "{{ php_versions | ansible.builtin.json_query('[?state==`absent`].version') | map('regex_replace', '^', '^php') }}"
+php_ver_absent: "{{ php_versions | ansible.builtin.json_query('[?state==`absent`].version') | sort }}"
+php_ver_absent_regex: "{{ php_versions | ansible.builtin.json_query('[?state==`absent`].version') | sort | map('regex_replace', '^', '^php') }}"
 
 # PHP versions present
 # cat defaults/main.yml | yq -o=json | jp "php_versions[?state=='present'].version"
-php_ver_present: "{{ php_versions | ansible.builtin.json_query('[?state==`present`].version') }}"
+php_ver_present: "{{ php_versions | ansible.builtin.json_query('[?state==`present`].version') | sort }}"
 
 # PHP packages absent
 # cat defaults/main.yml | yq -o=json | jp "php_versions[?state=='present'].pkg_absent[]" | sort -u
@@ -30,16 +30,28 @@ php_pkg_present: "{{ php_versions | ansible.builtin.json_query('[?state==`presen
 php_conf_ver_absent: "{{ php_config | ansible.builtin.json_query('[?state==`absent`].version') | sort }}"
 
 # PHP versions configuration directories absent
-php_conf_dirs_absent: "{{ php_config | ansible.builtin.json_query('[?state==`absent`].version') | map('regex_replace', '^', '/etc/php/') | sort }}"
+php_conf_dirs_absent: "{{ php_config | ansible.builtin.json_query('[?state==`absent`].version') | sort | map('regex_replace', '^', '/etc/php/') }}"
 
 # PHP versions configuration directories present
 # cat defaults/main.yml | yq -o=json | jp "php_config[?state=='present'].version"
-php_conf_dirs_present: "{{ php_config | ansible.builtin.json_query('[?state==`present`].version') | sort }}"
+php_conf_dirs_present: "{{ php_config | ansible.builtin.json_query('[?state==`present`].version') | sort | map('regex_replace', '^', '/etc/php/') }}"
 
 # PHP versions present files absent
 # cat defaults/main.yml | yq -o=json | jp "php_config[?state=='present'].files[]|[?state=='absent'].path"
 php_conf_files_absent: "{{ php_config | ansible.builtin.json_query('[?state==`present`].files[]|[?state==`absent`].path') | sort }}"
 
+# PHP versions present configuration files edited
+# cat defaults/main.yml | yq -o=json | jp "php_config[?state=='present'].files[]|[?state=='edited'].path"
+php_conf_files_edited: "{{ php_config | ansible.builtin.json_query('[?state==`present`].files[]|[?state==`edited`].path') | sort }}"
+
+# PHP versions present configuration files present
+# cat defaults/main.yml | yq -o=json | jp "php_config[?state=='present'].files[]|[?state=='present'].path"
+php_conf_files_present: "{{ php_config | ansible.builtin.json_query('[?state==`present`].files[]|[?state==`present`].path') | sort }}"
+
+# PHP versions present configuration files templated
+# cat defaults/main.yml | yq -o=json | jp "php_config[?state=='present'].files[]|[?state=='templated'].path"
+php_conf_files_templated: "{{ php_config | ansible.builtin.json_query('[?state==`present`].files[]|[?state==`templated`].path') | sort }}"
+
 # GPG public key URL linked from
 # https://packages.sury.org/php/
 php_gpg_url: https://packages.sury.org/php/apt.gpg