diff --git a/config/sets/laravel-code-quality.php b/config/sets/laravel-code-quality.php
index e8ba5935..66d12109 100644
--- a/config/sets/laravel-code-quality.php
+++ b/config/sets/laravel-code-quality.php
@@ -4,10 +4,12 @@
use Rector\Config\RectorConfig;
use RectorLaravel\Rector\Assign\CallOnAppArrayAccessToStandaloneAssignRector;
+use RectorLaravel\Rector\Coalesce\ApplyDefaultInsteadOfNullCoalesceRector;
use RectorLaravel\Rector\MethodCall\ReverseConditionableMethodCallRector;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->import(__DIR__ . '/../config.php');
$rectorConfig->rule(CallOnAppArrayAccessToStandaloneAssignRector::class);
$rectorConfig->rule(ReverseConditionableMethodCallRector::class);
+ $rectorConfig->rule(ApplyDefaultInsteadOfNullCoalesceRector::class);
};
diff --git a/docs/rector_rules_overview.md b/docs/rector_rules_overview.md
index 906f4dea..3be2e93d 100644
--- a/docs/rector_rules_overview.md
+++ b/docs/rector_rules_overview.md
@@ -1,4 +1,4 @@
-# 71 Rules Overview
+# 72 Rules Overview
## AbortIfRector
@@ -221,6 +221,21 @@ Replace `$app->environment() === 'local'` with `$app->environment('local')`
+## ApplyDefaultInsteadOfNullCoalesceRector
+
+Apply default instead of null coalesce
+
+:wrench: **configure it!**
+
+- class: [`RectorLaravel\Rector\Coalesce\ApplyDefaultInsteadOfNullCoalesceRector`](../src/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector.php)
+
+```diff
+-custom_helper('app.name') ?? 'Laravel';
++custom_helper('app.name', 'Laravel');
+```
+
+
+
## ArgumentFuncCallToMethodCallRector
Move help facade-like function calls to constructor injection
diff --git a/src/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector.php b/src/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector.php
new file mode 100644
index 00000000..6167dc31
--- /dev/null
+++ b/src/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector.php
@@ -0,0 +1,136 @@
+applyDefaultWith = self::defaultLaravelMethods();
+ }
+
+ /**
+ * @return ApplyDefaultInsteadOfNullCoalesce[]
+ */
+ public static function defaultLaravelMethods(): array
+ {
+ return [
+ new ApplyDefaultInsteadOfNullCoalesce('config'),
+ new ApplyDefaultInsteadOfNullCoalesce('env'),
+ new ApplyDefaultInsteadOfNullCoalesce('data_get', argumentPosition: 2),
+ new ApplyDefaultInsteadOfNullCoalesce('input', new ObjectType('Illuminate\Http\Request')),
+ new ApplyDefaultInsteadOfNullCoalesce('get', new ObjectType('Illuminate\Support\Env')),
+ ];
+ }
+
+ public function getRuleDefinition(): RuleDefinition
+ {
+ return new RuleDefinition(
+ 'Apply default instead of null coalesce',
+ [
+ new ConfiguredCodeSample(
+ <<<'CODE_SAMPLE'
+custom_helper('app.name') ?? 'Laravel';
+CODE_SAMPLE,
+ <<<'CODE_SAMPLE'
+custom_helper('app.name', 'Laravel');
+CODE_SAMPLE
+ ,
+ [
+ ...self::defaultLaravelMethods(),
+ new ApplyDefaultInsteadOfNullCoalesce('custom_helper'),
+ ],
+ ),
+ ]
+ );
+ }
+
+ public function getNodeTypes(): array
+ {
+ return [Coalesce::class];
+ }
+
+ /**
+ * @param Coalesce $node
+ */
+ public function refactor(Node $node): MethodCall|StaticCall|FuncCall|null
+ {
+ if (! $node->left instanceof FuncCall &&
+ ! $node->left instanceof MethodCall &&
+ ! $node->left instanceof StaticCall
+ ) {
+ return null;
+ }
+
+ if ($node->left->isFirstClassCallable()) {
+ return null;
+ }
+
+ $call = $node->left;
+
+ foreach ($this->applyDefaultWith as $applyDefaultWith) {
+ $valid = false;
+
+ $objectType = $call->var ?? $call->class ?? null;
+
+ if (
+ $applyDefaultWith->getObjectType() instanceof ObjectType &&
+ $objectType !== null &&
+ $this->isObjectType(
+ $objectType,
+ $applyDefaultWith->getObjectType()) &&
+ $this->isName($call->name, $applyDefaultWith->getMethodName())
+ ) {
+ $valid = true;
+ } elseif (
+ $applyDefaultWith->getObjectType() === null &&
+ $this->isName($call->name, $applyDefaultWith->getMethodName())
+ ) {
+ $valid = true;
+ }
+
+ if (! $valid) {
+ continue;
+ }
+
+ if (count($call->args) === $applyDefaultWith->getArgumentPosition()) {
+ if ($this->getType($node->right)->isNull()->yes()) {
+ return $call;
+ }
+ $call->args[count($call->args)] = new Arg($node->right);
+
+ return $call;
+ }
+ }
+
+ return null;
+ }
+
+ public function configure(array $configuration): void
+ {
+ Assert::allIsInstanceOf($configuration, ApplyDefaultInsteadOfNullCoalesce::class);
+ $this->applyDefaultWith = $configuration;
+ }
+}
diff --git a/src/ValueObject/ApplyDefaultInsteadOfNullCoalesce.php b/src/ValueObject/ApplyDefaultInsteadOfNullCoalesce.php
new file mode 100644
index 00000000..20766a80
--- /dev/null
+++ b/src/ValueObject/ApplyDefaultInsteadOfNullCoalesce.php
@@ -0,0 +1,27 @@
+objectType;
+ }
+
+ public function getMethodName(): string
+ {
+ return $this->methodName;
+ }
+
+ public function getArgumentPosition(): int
+ {
+ return $this->argumentPosition;
+ }
+}
diff --git a/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/ApplyDefaultInsteadOfNullCoalesceRectorTest.php b/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/ApplyDefaultInsteadOfNullCoalesceRectorTest.php
new file mode 100644
index 00000000..54726842
--- /dev/null
+++ b/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/ApplyDefaultInsteadOfNullCoalesceRectorTest.php
@@ -0,0 +1,31 @@
+doTestFile($filePath);
+ }
+
+ public function provideConfigFilePath(): string
+ {
+ return __DIR__ . '/config/configured_rule.php';
+ }
+}
diff --git a/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/Fixture/applies_based_on_argument_position.php.inc b/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/Fixture/applies_based_on_argument_position.php.inc
new file mode 100644
index 00000000..8a66bfd3
--- /dev/null
+++ b/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/Fixture/applies_based_on_argument_position.php.inc
@@ -0,0 +1,15 @@
+
+-----
+
diff --git a/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/Fixture/fixture.php.inc b/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/Fixture/fixture.php.inc
new file mode 100644
index 00000000..50560f87
--- /dev/null
+++ b/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/Fixture/fixture.php.inc
@@ -0,0 +1,25 @@
+input('value') ?? '';
+
+\Illuminate\Support\Env::get('APP_NAME') ?? '';
+
+?>
+-----
+input('value', '');
+
+\Illuminate\Support\Env::get('APP_NAME', '');
+
+?>
diff --git a/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/Fixture/skip_already_set_default.php.inc b/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/Fixture/skip_already_set_default.php.inc
new file mode 100644
index 00000000..81c39e37
--- /dev/null
+++ b/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/Fixture/skip_already_set_default.php.inc
@@ -0,0 +1,11 @@
+input('value', '') ?? '';
+
+\Illuminate\Support\Env::get('APP_NAME', '') ?? '';
+
+?>
diff --git a/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/Fixture/skip_non_matching_cases.php.inc b/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/Fixture/skip_non_matching_cases.php.inc
new file mode 100644
index 00000000..3feb94cc
--- /dev/null
+++ b/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/Fixture/skip_non_matching_cases.php.inc
@@ -0,0 +1,13 @@
+inputOf('value') ?? '';
+(new \Illuminate\Http\RequestElse())->input('value') ?? '';
+
+\Illuminate\Support\Env::getAgain('APP_NAME') ?? '';
+\Illuminate\Support\EnvUtil::get('APP_NAME') ?? '';
+
+?>
diff --git a/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/config/configured_rule.php b/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/config/configured_rule.php
new file mode 100644
index 00000000..755e2576
--- /dev/null
+++ b/tests/Rector/Coalesce/ApplyDefaultInsteadOfNullCoalesceRector/config/configured_rule.php
@@ -0,0 +1,12 @@
+import(__DIR__ . '/../../../../../config/config.php');
+
+ $rectorConfig->rule(ApplyDefaultInsteadOfNullCoalesceRector::class);
+};