This project provides a generic way to create a various set of project types. For each project type, an appropriate build configuration exists. The configuration describes how to build a project, e.g. which steps are necessary and what properties are required when rendering project templates.

External template packages#

Project templates are distributed through external Composer packages.


Each Composer package must be of the type project-builder-template.

2    "name": "cpsit/project-builder-template-my-fancy-project",
3    "type": "project-builder-template",
4    // ...

Additionally, the packages must be installable via Composer. There are three ways to make a template package available to the project builder:

  1. Either register it on Packagist,

  2. Use any other Composer registry (e.g. self-hosted Satis instance), or

  3. Host the project template on a VCS repository (such as GitHub) to make your package available to the project builder.

Once you use the project builder to create a new project, you can select the appropriate provider that hosts your template package.


You can add the cpsit/project-builder package as a dependency to your template package. Composer will correctly resolve the constraint. This way, you can define the versions of the project builder actually supported by your package:

2    "name": "cpsit/project-builder-template-my-fancy-project",
3    "type": "project-builder-template",
4    "require": {
5        "cpsit/project-builder": "^1.0"
6    }
7    // ...

File structure#

Within the external Composer template package, the following file structure must exist:

├── composer.json
├── config.yml
├── config
│   └── services.yaml
├── src
│   ├── ...
│   └── Twig
│       └── Function
│           └── MyCustomTwigFunction.php
└── templates
    ├── shared
    │   ├── ...
    │   └── my-fancy-shared-resource
    │       ├── composer.json
    │       └── templates
    │           └── src
    │               ├── ...
    │               └── .gitlab-ci.yml.twig
    └── src
        ├── ...
        └── composer.json.twig

In this example, the project type my-fancy-project is configured and distributed through the package cpsit/project-builder-template-my-fancy-project. It contains the following files and directories:

  • composer.json (optional) defines additional template dependencies. Those are installed by the build step installComposerDependencies. Read more at Processing build steps#Install Composer dependencies.

  • config.yml is the main configuration file. It contains all instructions on how to build new projects of this project type. Read more at Config file.

  • config (optional) contains additional service configuration files, e.g. services.yaml or services.php. Read more at Dependency injection#Extending service configuration.

  • src (optional) may contain additional PHP classes. Normally, these require an additional service configuration as described before.

  • templates (optional) contains various project source files. The following sub-folders are supported:

    • shared (optional) should contain shared source files. Those are normally created when installing Composer dependencies defined by composer.json. Read more at Shared source files.

    • src (optional) contains all project source files. Those can be either generic files to be copied to the generated project or Twig template files. Twig files are processed before copying them to the generated project. Read more at Source files.

Config file#

Each project type requires a configuration file. It describes how to build a new project of this type and is located in the template directory of the associated project type.

The following filename variants are supported:

  1. config.yml

  2. config.yaml

  3. config.json

See also

See ConfigReader::FILE_VARIANTS for an overview of supported filenames.


Each config file should at least contain the following properties:

  • name is kind of a label for the configured project type. It is mainly used for communication with the user, keeping the actual project type internal.

  • steps defines a list of necessary build steps. Those steps are processed once a new project of the associated project type is generated. Read more at Processing build steps.

Usually, it is also necessary to collect some more information from the user, e.g. to be able to prepare template files such as or composer.json.twig. For this, a set of properties can be defined. Those properties are then used to collect information in form of build instructions from the user. Read more at Processing build steps#Collect build instructions.


 1name: My fancy project
 4  - type: installComposerDependencies
 5  - type: collectBuildInstructions
 6  - type: processSourceFiles
 7    options:
 8      fileConditions:
 9        # You can define a Twig template to render...
10        - path: composer.json.twig
11          if: 'features["composer"]'
12        # ... or use static files
13        - path: example-v3.conf
14          if: 'features["example"] && example["version"] == "3"'
15          target: example.conf
16  - type: processSharedSourceFiles
17    options:
18      fileConditions:
19        # Use Symfony Expression Language to define file conditions
20        - path: phpunit.xml
21          if: 'features["phpunit"]'
22        # Mirror an entire directory
23        - path: 'source-dir/*'
24          target: 'target-dir/*'
25        # Apply Twig expression for custom target directory names
26        - path: 'source-dir/*'
27          target: '{{ | slugify }}-target-dir/*'
28  - type: mirrorProcessedFiles
29  - type: runCommand
30    options:
31      command: 'git init --initial-branch=main'
32      skipConfirmation: true
33  - type: showNextSteps
34    options:
35      templateFile: templates/next-steps.html.twig
38  # Project
39  - identifier: project
40    name: Project
41    properties:
42      - identifier: customer_name
43        name: Customer name
44        type: staticValue
45        validators:
46          - type: notEmpty
47      - identifier: project_name
48        name: Project name
49        type: staticValue
50        defaultValue: basic
51        validators:
52          - type: notEmpty
53          - type: regex
54            options:
55              pattern: '/^[a-zA-Z]+$/'
56              errorMessage: 'The project name should consist of letters only.'
58  # Features
59  - identifier: features
60    name: Features
61    properties:
62      - identifier: composer
63        name: Enable <comment>Composer</comment> support?
64        type: question
65        defaultValue: true
66      - identifier: phpstan
67        name: Do you need <comment>PHPStan</comment> support?
68        type: question
69      - identifier: phpunit
70        name: Do you want to run tests with <comment>PHPUnit</comment>?
71        type: question
73  # Author
74  - identifier: author
75    name: About you
76    properties:
77      - identifier: name
78        name: Your name
79        type: staticValue
80        validators:
81          - type: notEmpty
82      - identifier: email
83        name: Your e-mail address
84        type: staticValue
85        validators:
86          - type: notEmpty
87          - type: email

Twig integration#

Some configuration parts may be configured as Twig templates. For example, the defaultValue option of a configured property may contain the processed value of a previously added property:

 2  - identifier: project
 3    name: Project
 4    properties:
 5      - identifier: name
 6        name: Name
 7        type: staticValue
 8        validators:
 9          - type: notEmpty
10      - identifier: vendor
11        name: Vendor
12        type: staticValue
13        validators:
14          - type: notEmpty
15      - identifier: package_name
16        name: Package name
17        type: staticValue
18        defaultValue: '{{ project.vendor | slugify }}/{{ | slugify }}'
19        validators:
20          - type: notEmpty

The following configuration options are currently processed by the Twig renderer:

  • steps.*.options.fileConditions.*.target

  • properties.*.value

  • properties.*.properties.*.defaultValue

  • properties.*.properties.*.options.*.value

Symfony Expression Language integration#

Several configuration options use conditions to determine whether a property or step should be applied. All used conditions are parsed by the Symfony Expression Language.


2  - type: processSharedSourceFiles
3    options:
4      fileConditions:
5        - path: composer.json
6          if: 'features["composer"] == true'

The following configuration options are currently evaluated by the Symfony Expression Language:

  • steps.*.options.fileConditions.*.if

  • properties.*.if

  • properties.*.properties.*.if

  • properties.*.properties.*.options.*.if

Mapping and hydration#

Config files are located by the ConfigReader and parsed by the internal ConfigFactory. With the help of the fantastic external library cuyz/valinor, the parsed config file is mapped to an object structure of value objects. The final configuration ends up in an instance of Builder\Config\Config:

1$configReader = \CPSIT\ProjectBuilder\Builder\Config\ConfigReader::create();
2$config = $configReader->readConfig('my-fancy-project');
4echo $config->getIdentifier(); // my-fancy-project
5echo $config->getName(); // My fancy project

Each configured property in the config file is now accessible from the Config object:













See also

All hydrated value objects can be found at Builder\Config\ValueObject.


Config files are validated against a JSON schema. The schema file is located at resources/config.schema.json. Schema validation is handled by ConfigFactory::isValidConfig() with the help of the great external library justinrainbow/json-schema.


If a config file does not match the required schema, project generation will fail immediately.

Source files#

Each project type may provide several source files. They must be stored in a templates/src folder.

Currently, the following file variants are supported:

  • Generic files can be any files other than Twig files. They will be copied as-is to the generated project. Example: composer.json

  • Twig template files are pre-processed by the Twig renderer before they are copied to the generated project. The configured properties are used as template variables. Read more at Architecture#Template rendering. Example: composer.json.twig

Shared source files#

In case multiple project types share the same source files, it might be useful to outsource them to an external Composer package. This allows better maintenance of those shared source files. Per convention, external shared Composer packages should be of the type project-builder-shared.

2    "name": "my-vendor/my-fancy-shared-template",
3    "type": "project-builder-shared",
4    // ...

Integration into the template package#

The shared source file packages must be required in the composer.json file of each project type that requires the shared source files. As a consequence, the package must be installable via Composer.

The project builder expects shared source files to be installed within the project type’s templates/shared/<package-name>/templates/src folder. For this, it is useful to use the Composer package oomphinc/composer-installers-extender and define the installation paths of each shared source file package.

 2    "name": "my-vendor/my-fancy-project-template",
 3    "type": "project-builder-template",
 4    "require": {
 5        "my-vendor/my-fancy-shared-template": "^1.0",
 6        "oomphinc/composer-installers-extender": "^2.0"
 7    },
 8    "extra": {
 9        "installer-paths": {
10            "templates/shared/{$name}/": [
11                "type:project-builder-shared"
12            ]
13        },
14        "installer-types": [
15            "project-builder-shared"
16        ]
17    },
18    // ...

The shared source file package must then provide the following folder structure:

├── composer.json
└── templates
    └── src
        ├── ...
        └── some-shared-file.json.twig