diff --git a/InfinityloopCodingStandard/Sniffs/Classes/ConstructorPropertyPromotionSpacingSniff.php b/InfinityloopCodingStandard/Sniffs/Classes/ConstructorPropertyPromotionSpacingSniff.php
new file mode 100644
index 0000000..81b41bb
--- /dev/null
+++ b/InfinityloopCodingStandard/Sniffs/Classes/ConstructorPropertyPromotionSpacingSniff.php
@@ -0,0 +1,108 @@
+getTokens();
+
+ $namePointer = \SlevomatCodingStandard\Helpers\TokenHelper::findNextEffective($phpcsFile, $functionPointer + 1);
+
+ if (\strtolower($tokens[$namePointer]['content']) !== '__construct') {
+ return;
+ }
+
+ if (\SlevomatCodingStandard\Helpers\FunctionHelper::isAbstract($phpcsFile, $functionPointer)) {
+ return;
+ }
+
+ $parameterPointers = $this->getParameterPointers($phpcsFile, $functionPointer);
+
+ if (\count($parameterPointers) === 0) {
+ return;
+ }
+
+ $containsPropertyPromotion = false;
+
+ foreach ($parameterPointers as $parameterPointer) {
+ $pointerBefore = \SlevomatCodingStandard\Helpers\TokenHelper::findPrevious(
+ $phpcsFile,
+ [\T_COMMA, \T_OPEN_PARENTHESIS],
+ $parameterPointer - 1,
+ );
+
+ $visibilityPointer = \SlevomatCodingStandard\Helpers\TokenHelper::findNextEffective($phpcsFile, $pointerBefore + 1);
+
+ if (\in_array($tokens[$visibilityPointer]['code'], \PHP_CodeSniffer\Util\Tokens::$scopeModifiers, true)) {
+ $containsPropertyPromotion = true;
+ }
+ }
+
+ if (!$containsPropertyPromotion) {
+ return;
+ }
+
+ $previousPointer = null;
+
+ foreach ($parameterPointers as $parameterPointer) {
+ if ($previousPointer === null) {
+ $previousPointer = $parameterPointer;
+
+ continue;
+ }
+
+ if ($tokens[$previousPointer]['line'] !== $tokens[$parameterPointer]['line']) {
+ continue;
+ }
+
+ $fix = $phpcsFile->addFixableError(
+ 'Constructor parameter should be reformatted to next line.',
+ $parameterPointer,
+ self::CONSTRUCTOR_PARAMETER_SAME_LINE,
+ );
+
+ if (!$fix) {
+ continue;
+ }
+
+ $phpcsFile->fixer->beginChangeset();
+
+ $pointerBefore = \SlevomatCodingStandard\Helpers\TokenHelper::findPrevious(
+ $phpcsFile,
+ [\T_COMMA, \T_OPEN_PARENTHESIS],
+ $parameterPointer - 1,
+ );
+
+ $phpcsFile->fixer->addContent($pointerBefore, $phpcsFile->eolChar);
+
+ $phpcsFile->fixer->endChangeset();
+ }
+ }
+
+ private function getParameterPointers(\PHP_CodeSniffer\Files\File $phpcsFile, int $functionPointer) : array
+ {
+ $tokens = $phpcsFile->getTokens();
+
+ return \SlevomatCodingStandard\Helpers\TokenHelper::findNextAll(
+ $phpcsFile,
+ \T_VARIABLE,
+ $tokens[$functionPointer]['parenthesis_opener'] + 1,
+ $tokens[$functionPointer]['parenthesis_closer'],
+ );
+ }
+}
diff --git a/InfinityloopCodingStandard/ruleset.xml b/InfinityloopCodingStandard/ruleset.xml
index e618450..a939741 100644
--- a/InfinityloopCodingStandard/ruleset.xml
+++ b/InfinityloopCodingStandard/ruleset.xml
@@ -502,4 +502,5 @@
+
diff --git a/README.md b/README.md
index 3e1ce40..e89517e 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,10 @@ Checks that there is a certain number of blank lines between code and comment
Improved version of Slevomat UnionTypeHintFormat with added formatting of multiline unions
+#### InfinityloopCodingStandard.Classes.ConstructorPropertyPromotionSpacing :wrench:
+
+Space constructor arguments one per line when Constructor Property Promotion is used
+
### Slevomat sniffs
Detailed list of Slevomat sniffs with configured settings. Some sniffs are not included, either because we dont find them helpful, the are too strict, or collide with their counter-sniff (require/disallow pairs).