From 2a82b5c0b1d62ecbb4478a7064acbc7a9019b8e9 Mon Sep 17 00:00:00 2001 From: pcm-internal <> Date: Thu, 27 Nov 2025 13:22:07 +0000 Subject: [PATCH] Initial commit --- .editorconfig | 14 ++++ .gitignore | 7 ++ .php-cs-fixer.dist.php | 32 ++++++++ Containerfile | 6 ++ LICENSE | 7 ++ Makefile | 19 +++++ README.md | 127 +++++++++++++++++++++++++++++ bin/quickstart | 19 +++++ compose.yml | 7 ++ composer.json | 40 +++++++++ config/bundles.php | 14 ++++ config/definition.php | 18 ++++ config/services.yaml | 28 +++++++ phpunit.xml.dist | 19 +++++ src/Greeting.php | 18 ++++ src/PcmExampleBundle.php | 50 ++++++++++++ tests/Config/ConfigurationTest.php | 45 ++++++++++ tests/TestKernel.php | 26 ++++++ 18 files changed, 496 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .php-cs-fixer.dist.php create mode 100644 Containerfile create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100755 bin/quickstart create mode 100644 compose.yml create mode 100644 composer.json create mode 100644 config/bundles.php create mode 100644 config/definition.php create mode 100644 config/services.yaml create mode 100644 phpunit.xml.dist create mode 100644 src/Greeting.php create mode 100644 src/PcmExampleBundle.php create mode 100644 tests/Config/ConfigurationTest.php create mode 100644 tests/TestKernel.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c9dc060 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# This is the top-most .editorconfig file; do not search in parent directories. +root = true + +# All files. +[*] +end_of_line = LF +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a9040a --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.vscode +/vendor +composer.lock +.phpunit.result.cache +/var +.php-cs-fixer.cache diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..bb2d4ed --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,32 @@ +setRules([ + '@Symfony' => true, + + 'binary_operator_spaces' => [ + 'operators' => [ + '=' => 'align_single_space', + '=>' => 'align_single_space', + ], + ], + + 'single_blank_line_at_eof' => true, + 'phpdoc_align' => false, + 'phpdoc_to_comment' => false, + 'strict_comparison' => true, + 'declare_strict_types' => true, + 'single_trait_insert_per_statement' => false, + 'nullable_type_declaration_for_default_null_value' => true, + 'increment_style' => [ + 'style' => 'post', + ], + ]) + ->setFinder( + (new PhpCsFixer\Finder()) + ->in(__DIR__) + ->exclude('var') + ) +; diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..1105963 --- /dev/null +++ b/Containerfile @@ -0,0 +1,6 @@ +FROM php:8.3-alpine + +WORKDIR /code +COPY --from=composer:2 /usr/bin/composer /usr/bin/composer +COPY ./ /code + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ec60b2d --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2024 PCM + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eb3076e --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +PHP = docker compose run php + +.PHONY: composer_install composer_update static_analysis tests remove_orphans + +composer_install: + @$(PHP) composer install + +composer_update: + @$(PHP) composer update + +static_analysis: + @$(PHP) vendor/bin/phpstan analyse src --level 6 + +tests: + @$(PHP) rm -rf var/cache + @$(PHP) vendor/bin/phpunit + +remove_orphans: + @docker compose down --remove-orphans diff --git a/README.md b/README.md new file mode 100644 index 0000000..d1140cc --- /dev/null +++ b/README.md @@ -0,0 +1,127 @@ +# Bundle Skeleton + +A skeleton project for creating Symfony bundles. + +This skeleton is a very basic bundle with comments explaining what various bits are doing. + +The bundle is called `PcmExampleBundle` and contains a `Greeting` class with a `greet(): string` method. + +The `greet()` method returns a string welcoming someone who's name you can specify in the bundle configuration file. + + +# Quickstart + +There is a `quickstart` shell script located at `bin/quickstart`. + +Running this script will prompt you to enter a PascalCase name for your bundle, for example if you wanted to create a bundle called `pcm/my-cool-bundle` you should enter `MyCoolBundle`. + +This performs a crude search and replace across the example bundle to update the class names and namespaces before installing the project dependencies. + +> You will still need to manually update the repository name and description in `composer.json`. + + +# Adding a deploy key to new repositories + +When this project is used as a template the resulting repository will need a deploy token associated with it before other +projects can `composer install` it. + +This is done in Gitea by going to Settings -> Deploy Keys -> Add Deploy Key, then pasting in the deploy key's public key. + +The deploy key in question is located in Keeper under "Deploy key for Piermont bundles". + + +# Installing dependencies during development + +You need to run `docker compose run php` before any composer commands. EG: +```sh +docker compose run php composer require symfony/twig-bundle +``` +The Makefile has some common shorthands as usual. + + +# PHPUnit testing + +The bundle and it's configuration can be tested with PHPUnit. + + +# Installing a development version of the bundle + +If you need to install the bundle to test stuff (EG to see how certain Twig templates look, etc) you can do so +by using a development version of the bundle. + +### Preparing the composer.json file + +First, make sure your Symfony project has the following in it's `composer.json` file: +```json +{ + "minimum-stability": "dev", + "prefer-stable": true, +} +``` + +Next, you need to add the repository to the `composer.json` file, just as you would any other PCM bundle: +```json +{ + "repositories": [ + { + "type": "vcs", + "url": "ssh://example/bundle.git" + }, + ] +} +``` + +### Installing the development bundle + +You can now install the bundle with composer. Because the bundle does not yet have a tagged version we +have to specify that it's a dev bundle alongside a branch to use. For example: +```sh +composer require pcm/example-bundle:dev-develop +``` + +The `dev-develop` part is specifying both that it's a **dev** package and that we want to use the **develop** branch. If you wanted to use the `master` branch you would specify as so: `dev-master`. + +> If your project has `minimum-stability: dev` set you can run a composer install without specifying the dev version and it will automatically install a dev version with the repo's default branch. I think that specifying the dev branch manually is less confusing though so I'd recommend against doing it this way. + +If a flex recipe is present it will prompt you to install it. + +**NOTE** If a flex recipe with a dev version set as it's minimum version is present and installed, the "symfony.lock" file will generate an incorrect version number "develop.9999999". This causes issues when uninstalling, so manually change this value to be `dev-develop` or whichever version you installed. + + +### Updating the development bundle + +You can make changes to the bundle whilst it's installed. Once you've pushed your changes with git you can run a composer update to retrieve the most recent changes: +```sh +composer update pcm/example-bundle +``` + +### Uninstalling your development bundle + +Make sure that the bundle version is correct in the `symfony.lock` file (see above) before running the usual uninstall command: +```sh +composer remove pcm/example-bundle +``` + + +# Versioning + +Once you have a version of your bundle you are happy with you can give it a tag. + +Follow [semantic versioning](https://semver.org/#summary) and tag the first release as `0.1.0`. +```sh +git tag 0.1.0 +git push origin 0.1.0 +``` + +Now you should be able to install the package by running the `composer require` command without an additional version: +```sh +composer require pcm/example-bundle +``` + + +# Creating a flex recipe + +I recommend not creating a recipe until at least the first tagged release of your bundle. This is because creating a flex recipe with `dev-develop` as it's version number works, however it causes an error in the `symfony.lock` file (see above). This can be resolved by manually editing the file, but it's still a bit annoying. + +See the flex recipe repository for info on how to create a flex recipe. + diff --git a/bin/quickstart b/bin/quickstart new file mode 100755 index 0000000..035d8a9 --- /dev/null +++ b/bin/quickstart @@ -0,0 +1,19 @@ +#!/bin/bash + +read -p "If your bundle is going to be \"pcm/example-bundle\", type \"ExampleBundle\". +Please enter the name of your bundle: " ANS + +echo "Changing ExampleBundle to $ANS." + +# When running sed, the `-i "" e` is for MacOS, and I think it writes the files instead of sending to stdout? +find . -type f ! -name "quickstart" ! -name "README.md" ! -name "LICENSE" ! -path "*/.*" ! -wholename "*/vendor/*" -exec sed -i "" -e "s/ExampleBundle/$ANS/g" {} ';' + +# Rename PcmExampleBundle +mv src/PcmExampleBundle.php src/Pcm${ANS}.php + +echo "Ok that's probably worked, check git to make sure though." +echo "Also don't forget to update the name and description key in composer.json if you haven't already!" + +echo "Installing dependencies..." +docker compose run php composer install + diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..a8b7778 --- /dev/null +++ b/compose.yml @@ -0,0 +1,7 @@ +services: + php: + build: + context: . + dockerfile: Containerfile + volumes: + - ./:/code diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..5946fdc --- /dev/null +++ b/composer.json @@ -0,0 +1,40 @@ +{ + "name": "pcm/example-bundle", + "description": "PCM Example Bundle", + "type": "symfony-bundle", + "license": "MIT", + "authors": [ + { + "name": "Bradley Goode", + "email": "bg@pcmsystems.co.uk" + }, + { + "name": "Matt Feeney", + "email": "mf@pcmsystems.co.uk" + } + ], + + "autoload": { + "psr-4": { + "Pcm\\ExampleBundle\\": "src/" + } + }, + + "autoload-dev": { + "psr-4": { + "Pcm\\ExampleBundle\\Tests\\": "tests/" + } + }, + + "require": { + "symfony/dependency-injection": "^7.1", + "symfony/framework-bundle": "^7.1", + "symfony/yaml": "^7.1" + }, + + "require-dev": { + "symfony/test-pack": "^1.1", + "friendsofphp/php-cs-fixer": "^3.61", + "phpstan/phpstan": "^1.12" + } +} diff --git a/config/bundles.php b/config/bundles.php new file mode 100644 index 0000000..bf25021 --- /dev/null +++ b/config/bundles.php @@ -0,0 +1,14 @@ + ['all' => true], +]; + diff --git a/config/definition.php b/config/definition.php new file mode 100644 index 0000000..8c6eb92 --- /dev/null +++ b/config/definition.php @@ -0,0 +1,18 @@ +rootNode() + ->children() + ->scalarNode('name')->defaultValue('Mr. NoName')->cannotBeEmpty()->end() + ->end() + ; +}; + diff --git a/config/services.yaml b/config/services.yaml new file mode 100644 index 0000000..382ef0b --- /dev/null +++ b/config/services.yaml @@ -0,0 +1,28 @@ +services: + _defaults: + autowire: true + autoconfigure: true + + # Hide the fully qualified Greeting class name from public view + # but give the class an alias. This alias will be made public instead. + # + # Read the documentation to see why we do this: + # https://symfony.com/doc/current/service_container/autowiring.html#service-autowiring-alias + # + Pcm\ExampleBundle\Greeting: + public: false + alias: pcm_example.greeting + + + # Mark the alias we created as public. + pcm_example.greeting: + public: true + class: Pcm\ExampleBundle\Greeting + + + # If we were defining a twig extension, we'd want to add the twig.runtime tag + # so it loads correctly. + # + # Pcm\ExampleBundle\SomeTwigRuntime + # tags: + # - { name: twig.runtime } diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..60cb564 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + ./src + + + + + + + + + + + ./tests + + + diff --git a/src/Greeting.php b/src/Greeting.php new file mode 100644 index 0000000..7aa7f91 --- /dev/null +++ b/src/Greeting.php @@ -0,0 +1,18 @@ +name); + } +} + diff --git a/src/PcmExampleBundle.php b/src/PcmExampleBundle.php new file mode 100644 index 0000000..fcc189c --- /dev/null +++ b/src/PcmExampleBundle.php @@ -0,0 +1,50 @@ + $config + */ + public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void + { + /** + * Load the services defined in services.yaml into the container. + */ + $container->import('../config/services.yaml'); + + /** + * The "$config" variable contains an array representing the + * configuration and it's values. + * + * We can use it to configure the service container, for example + * by passing in arguments to any services we have defined. + * + * (see services.yaml) + */ + $container->services() + ->get('pcm_example.greeting') + ->arg('$name', $config['name']) + ; + } + + public function configure(DefinitionConfigurator $definition): void + { + /** + * Import the config definition (see definition.php). + */ + $definition->import('../config/definition.php'); + } +} + diff --git a/tests/Config/ConfigurationTest.php b/tests/Config/ConfigurationTest.php new file mode 100644 index 0000000..01c9ff8 --- /dev/null +++ b/tests/Config/ConfigurationTest.php @@ -0,0 +1,45 @@ +configuration = new Configuration(true); + $this->processor = new Processor(); + } + + public function testThrowsIfNameIsEmpty() + { + $config = $this->getValidConfig(); + $config['name'] = ''; + $this->expectException(\Exception::class); + $this->validateConfig($config); + } + + private function validateConfig(array $config): array + { + return $this->processor->processConfiguration($this->configuration, [$config]); + } + + private function getValidConfig(): array + { + return [ + 'name' => 'Boris', + ]; + } +} + diff --git a/tests/TestKernel.php b/tests/TestKernel.php new file mode 100644 index 0000000..11728ac --- /dev/null +++ b/tests/TestKernel.php @@ -0,0 +1,26 @@ +load(__DIR__.'/../config/packages/pcm_example.yaml'); + } +} +