Initial commit

This commit is contained in:
pcm-internal
2025-11-27 13:22:07 +00:00
commit 2a82b5c0b1
18 changed files with 496 additions and 0 deletions

14
.editorconfig Normal file
View File

@@ -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

7
.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
.DS_Store
.vscode
/vendor
composer.lock
.phpunit.result.cache
/var
.php-cs-fixer.cache

32
.php-cs-fixer.dist.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
return (new PhpCsFixer\Config())
->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')
)
;

6
Containerfile Normal file
View File

@@ -0,0 +1,6 @@
FROM php:8.3-alpine
WORKDIR /code
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
COPY ./ /code

7
LICENSE Normal file
View File

@@ -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.

19
Makefile Normal file
View File

@@ -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

127
README.md Normal file
View File

@@ -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.

19
bin/quickstart Executable file
View File

@@ -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

7
compose.yml Normal file
View File

@@ -0,0 +1,7 @@
services:
php:
build:
context: .
dockerfile: Containerfile
volumes:
- ./:/code

40
composer.json Normal file
View File

@@ -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"
}
}

14
config/bundles.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
/**
* Here we define the environments the bundle is allowed to be in.
*
* Most of the time we probably want our bundle to be present in
* all environments (prod, dev, test etc).
*/
return [
Pcm\ExampleBundle\PcmExampleBundle::class => ['all' => true],
];

18
config/definition.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
/**
* This file defines the layout of the configuration, type constraints
* and any default argument values.
*/
return static function (DefinitionConfigurator $definition): void {
$definition->rootNode()
->children()
->scalarNode('name')->defaultValue('Mr. NoName')->cannotBeEmpty()->end()
->end()
;
};

28
config/services.yaml Normal file
View File

@@ -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 }

19
phpunit.xml.dist Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" backupGlobals="false" colors="true" bootstrap="./vendor/autoload.php">
<coverage>
<include>
<directory>./src</directory>
</include>
</coverage>
<php>
<ini name="error_reporting" value="-1"/>
<ini name="intl.default_locale" value="en"/>
<ini name="intl.error_level" value="0"/>
<ini name="memory_limit" value="-1"/>
</php>
<testsuites>
<testsuite name="Test suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
</phpunit>

18
src/Greeting.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Pcm\ExampleBundle;
final class Greeting
{
public function __construct(private string $name)
{
}
public function greetPerson(): string
{
return sprintf("Hello there %s! Hope you're well.", $this->name);
}
}

50
src/PcmExampleBundle.php Normal file
View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Pcm\ExampleBundle;
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
/**
* This class represents the bundle.
*/
final class PcmExampleBundle extends AbstractBundle
{
/**
* @param array<string, mixed> $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');
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Pcm\ExampleBundle\Tests\Config;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Configuration;
use Symfony\Component\Config\Definition\Processor;
/**
* This file tests the config to ensure that the config validation is working as expected.
*/
class ConfigurationTest extends TestCase
{
private Configuration $configuration;
private Processor $processor;
protected function setUp(): void
{
$this->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',
];
}
}

26
tests/TestKernel.php Normal file
View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Pcm\ExampleBundle\Tests;
use Pcm\ExampleBundle\PcmExampleBundle;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\HttpKernel\Kernel;
final class TestKernel extends Kernel
{
public function registerBundles(): array
{
return [
new PcmExampleBundle()
];
}
public function registerContainerConfiguration(LoaderInterface $loader): void
{
// Ignore this
// $loader->load(__DIR__.'/../config/packages/pcm_example.yaml');
}
}