Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Extdn/Samples/Classes/ObjectInstantiation.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Extdn\Samples\Classes;

class ObjectInstantiation
{
public function instantiateSomeObjects()
{
new \DateTime('yesterday');

new stdclass;

$className = 'stdclass';

new $className;

$anonymous = new class implements \Countable {
public function count()
{
return 0;
}
};

$e = new Exception;

throw $e;

throw new Exception;
}
}
4 changes: 4 additions & 0 deletions Extdn/ruleset.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,10 @@
<rule ref="MEQP2.Templates.ThisInTemplate">
<exclude-pattern>*.php</exclude-pattern>
</rule>
<rule ref="MEQP2.Classes.ObjectInstantiation">
<exclude-pattern>*/Test/*</exclude-pattern>
<severity>1</severity>
</rule>
<rule ref="Extdn.Blocks.SetTemplateInBlock"/>
<rule ref="Extdn.Classes.StrictTypes"/>
<rule ref="Extdn.Templates.TemplateObjectManager"/>
Expand Down
183 changes: 183 additions & 0 deletions MEQP2/Sniffs/Classes/ObjectInstantiationSniff.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
# Rule: Keyword `new` should not be used
## Background

The `new` keyword should not be used to instantiate new classes. Constructor DI should be used instead:
Inject a factory (e.g. `ProductInterfaceFactory`) which can be generated automatically, Magento then takes care of
which actual class to instantiate.


**This is an experimental rule, only applied with lowest severity treshold of 1.**

## Reasoning
Magento uses constructor dependency injection with autowiring. It automatically generates interceptors that extend the
original class to add plugins.

It is encouraged to use these mechanisms instead of instantiating objects directly with `new`. This way, Magento can transparently
replace the concrete class with an interceptor or alternative implementations based on DI preference configuration.

This flexibility with plugins and preferences is lost if classes are instantiated directly.

### Exceptions

- Instantiating exceptions with `new` is allowed, there is no need for alternative implementations or plugins in exceptions
- The rule does not apply to unit tests, where automatic constructor DI is not present

### Dispute

**Classes from PHP core or non-Magento libraries:** While it is possible to generate factories for them too, it is
considered overkill by many.

Compare

```php
new DateTime('yesterday')
```
vs
```php
$this->dateTimeFactory->create(['time' => 'yesterday'])
```

**Data transport objects:** Data transport objects (DTOs) are often used in events to allow changing values in observers:

`new \Magento\Framework\DataObject(['key' => 'value])`

Using factories seems unnecessary too here, and Magento does not do it in the core either.

As an alternative, since `DataObject` is not more than a glorified array with magic getters and setters, an `stdclass` object could be used instead, circumventing
the `new` rule: `(object)['key' => 'value']`

**Anonymous classes:** For anonymous classes there is no alternative without `new` and they might be useful as
"private classes", i.e. internal to a module, where plugins are not useful or desired.

## How it works
Any usage of `new` that is not preceded by `throw` is considered a violation

## How to fix

Given code like this:

```php
namespace Lotr;

use Lotr\Ring;

class Sauron
{
public function forgeTheOneRing()
{
$ring = new Ring('Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul');
$this->putOnFinger($ring);
}
}
```
you would introduce constructor dependency injection with an autogenerated factory like this:

```php
namespace Lotr;

use Lotr\RingFactory;

class Sauron
{
/**
* @var RingFactory
*/
private $ringFactory;

public function __construct(RingFactory $ringFactory)
{
$this->ringFactory = $ringFactory;
}

public function forgeTheOneRing()
{
$ring = $this->ringFactory->create(
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
$this->putOnFinger($ring);
}
}
```

Note that the argument for **create** is always an array where the keys have to equal the constructor argument names for
the class to be instantiated. So the example above assumes a constructor signature for `Ring` like:

```php
public function __construct(string $inscription);
```

If there is an **interface** for `Ring`, e.g. `RingInterface` it is recommended to use a generated factory for the interface, so that the
actual implementation may be exchanged:

```php
public function __construct(RingInterfaceFactory $ringFactory)
```

### Special case: Custom factory

Sometimes, the automatically generated factories do not quite serve our needs and more specific creation methods than
the generic `create` are helpful. You can **create your own factory implementation**, Magento will only generate a
factory class if it cannot find an existing one.

Let's say, we want specific factory methods for different rings, we could implement our own factory class like this:

```php
namespace Lotr;

class RingFactory
{
public function createElvenRing() { ... }
public function createDwarfRing() { ... }
public function createHumanRing() { ... }
public function createTheOneRing() { ... }
}
```

In this factory, we still won't use the `new` keyword, but instead the object manager can be used (**factories are an exception to the rule "Never use the object manager directly"!**)

```php
namespace Lotr;

use Lotr\Ring;

class RingFactory
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;

public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}

public function createTheOneRing(): Ring
{
return $this->objectManager->create(
Ring::class,
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
}
}
```

**Do not use the object manager like this in anything else than factory or builder classes!** In other words, object
creation and object usage must be separated.

The signature of `ObjectManager::create()` is similar to the `create()` method of the automatically generated factories,
but additionally needs a class or interface name as first argument.

With such a custom factory, Sauron can forge the ring and let the "ring factory" decide about the inscription:

```php
public function forgeTheOneRing()
{
$ring = $this->ringFactory->createTheOneRing();
$this->putOnFinger($ring);
}
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Extdn/Samples/Classes/ObjectInstantiation.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Extdn\Samples\Classes;

class ObjectInstantiation
{
public function instantiateSomeObjects()
{
new \DateTime('yesterday');

new stdclass;

$className = 'stdclass';

new $className;

$anonymous = new class implements \Countable {
public function count()
{
return 0;
}
};

$e = new Exception;

throw $e;

throw new Exception;
}
}
4 changes: 4 additions & 0 deletions Extdn/ruleset.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,10 @@
<rule ref="MEQP2.Templates.ThisInTemplate">
<exclude-pattern>*.php</exclude-pattern>
</rule>
<rule ref="MEQP2.Classes.ObjectInstantiation">
<exclude-pattern>*/Test/*</exclude-pattern>
<severity>1</severity>
</rule>
<rule ref="Extdn.Blocks.SetTemplateInBlock"/>
<rule ref="Extdn.Classes.StrictTypes"/>
<rule ref="Extdn.Templates.TemplateObjectManager"/>
Expand Down
183 changes: 183 additions & 0 deletions MEQP2/Sniffs/Classes/ObjectInstantiationSniff.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
# Rule: Keyword `new` should not be used
## Background

The `new` keyword should not be used to instantiate new classes. Constructor DI should be used instead:
Inject a factory (e.g. `ProductInterfaceFactory`) which can be generated automatically, Magento then takes care of
which actual class to instantiate.


**This is an experimental rule, only applied with lowest severity treshold of 1.**

## Reasoning
Magento uses constructor dependency injection with autowiring. It automatically generates interceptors that extend the
original class to add plugins.

It is encouraged to use these mechanisms instead of instantiating objects directly with `new`. This way, Magento can transparently
replace the concrete class with an interceptor or alternative implementations based on DI preference configuration.

This flexibility with plugins and preferences is lost if classes are instantiated directly.

### Exceptions

- Instantiating exceptions with `new` is allowed, there is no need for alternative implementations or plugins in exceptions
- The rule does not apply to unit tests, where automatic constructor DI is not present

### Dispute

**Classes from PHP core or non-Magento libraries:** While it is possible to generate factories for them too, it is
considered overkill by many.

Compare

```php
new DateTime('yesterday')
```
vs
```php
$this->dateTimeFactory->create(['time' => 'yesterday'])
```

**Data transport objects:** Data transport objects (DTOs) are often used in events to allow changing values in observers:

`new \Magento\Framework\DataObject(['key' => 'value])`

Using factories seems unnecessary too here, and Magento does not do it in the core either.

As an alternative, since `DataObject` is not more than a glorified array with magic getters and setters, an `stdclass` object could be used instead, circumventing
the `new` rule: `(object)['key' => 'value']`

**Anonymous classes:** For anonymous classes there is no alternative without `new` and they might be useful as
"private classes", i.e. internal to a module, where plugins are not useful or desired.

## How it works
Any usage of `new` that is not preceded by `throw` is considered a violation

## How to fix

Given code like this:

```php
namespace Lotr;

use Lotr\Ring;

class Sauron
{
public function forgeTheOneRing()
{
$ring = new Ring('Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul');
$this->putOnFinger($ring);
}
}
```
you would introduce constructor dependency injection with an autogenerated factory like this:

```php
namespace Lotr;

use Lotr\RingFactory;

class Sauron
{
/**
* @var RingFactory
*/
private $ringFactory;

public function __construct(RingFactory $ringFactory)
{
$this->ringFactory = $ringFactory;
}

public function forgeTheOneRing()
{
$ring = $this->ringFactory->create(
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
$this->putOnFinger($ring);
}
}
```

Note that the argument for **create** is always an array where the keys have to equal the constructor argument names for
the class to be instantiated. So the example above assumes a constructor signature for `Ring` like:

```php
public function __construct(string $inscription);
```

If there is an **interface** for `Ring`, e.g. `RingInterface` it is recommended to use a generated factory for the interface, so that the
actual implementation may be exchanged:

```php
public function __construct(RingInterfaceFactory $ringFactory)
```

### Special case: Custom factory

Sometimes, the automatically generated factories do not quite serve our needs and more specific creation methods than
the generic `create` are helpful. You can **create your own factory implementation**, Magento will only generate a
factory class if it cannot find an existing one.

Let's say, we want specific factory methods for different rings, we could implement our own factory class like this:

```php
namespace Lotr;

class RingFactory
{
public function createElvenRing() { ... }
public function createDwarfRing() { ... }
public function createHumanRing() { ... }
public function createTheOneRing() { ... }
}
```

In this factory, we still won't use the `new` keyword, but instead the object manager can be used (**factories are an exception to the rule "Never use the object manager directly"!**)

```php
namespace Lotr;

use Lotr\Ring;

class RingFactory
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;

public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}

public function createTheOneRing(): Ring
{
return $this->objectManager->create(
Ring::class,
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
}
}
```

**Do not use the object manager like this in anything else than factory or builder classes!** In other words, object
creation and object usage must be separated.

The signature of `ObjectManager::create()` is similar to the `create()` method of the automatically generated factories,
but additionally needs a class or interface name as first argument.

With such a custom factory, Sauron can forge the ring and let the "ring factory" decide about the inscription:

```php
public function forgeTheOneRing()
{
$ring = $this->ringFactory->createTheOneRing();
$this->putOnFinger($ring);
}
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Extdn/Samples/Classes/ObjectInstantiation.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Extdn\Samples\Classes;

class ObjectInstantiation
{
public function instantiateSomeObjects()
{
new \DateTime('yesterday');

new stdclass;

$className = 'stdclass';

new $className;

$anonymous = new class implements \Countable {
public function count()
{
return 0;
}
};

$e = new Exception;

throw $e;

throw new Exception;
}
}
4 changes: 4 additions & 0 deletions Extdn/ruleset.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,10 @@
<rule ref="MEQP2.Templates.ThisInTemplate">
<exclude-pattern>*.php</exclude-pattern>
</rule>
<rule ref="MEQP2.Classes.ObjectInstantiation">
<exclude-pattern>*/Test/*</exclude-pattern>
<severity>1</severity>
</rule>
<rule ref="Extdn.Blocks.SetTemplateInBlock"/>
<rule ref="Extdn.Classes.StrictTypes"/>
<rule ref="Extdn.Templates.TemplateObjectManager"/>
Expand Down
183 changes: 183 additions & 0 deletions MEQP2/Sniffs/Classes/ObjectInstantiationSniff.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
# Rule: Keyword `new` should not be used
## Background

The `new` keyword should not be used to instantiate new classes. Constructor DI should be used instead:
Inject a factory (e.g. `ProductInterfaceFactory`) which can be generated automatically, Magento then takes care of
which actual class to instantiate.


**This is an experimental rule, only applied with lowest severity treshold of 1.**

## Reasoning
Magento uses constructor dependency injection with autowiring. It automatically generates interceptors that extend the
original class to add plugins.

It is encouraged to use these mechanisms instead of instantiating objects directly with `new`. This way, Magento can transparently
replace the concrete class with an interceptor or alternative implementations based on DI preference configuration.

This flexibility with plugins and preferences is lost if classes are instantiated directly.

### Exceptions

- Instantiating exceptions with `new` is allowed, there is no need for alternative implementations or plugins in exceptions
- The rule does not apply to unit tests, where automatic constructor DI is not present

### Dispute

**Classes from PHP core or non-Magento libraries:** While it is possible to generate factories for them too, it is
considered overkill by many.

Compare

```php
new DateTime('yesterday')
```
vs
```php
$this->dateTimeFactory->create(['time' => 'yesterday'])
```

**Data transport objects:** Data transport objects (DTOs) are often used in events to allow changing values in observers:

`new \Magento\Framework\DataObject(['key' => 'value])`

Using factories seems unnecessary too here, and Magento does not do it in the core either.

As an alternative, since `DataObject` is not more than a glorified array with magic getters and setters, an `stdclass` object could be used instead, circumventing
the `new` rule: `(object)['key' => 'value']`

**Anonymous classes:** For anonymous classes there is no alternative without `new` and they might be useful as
"private classes", i.e. internal to a module, where plugins are not useful or desired.

## How it works
Any usage of `new` that is not preceded by `throw` is considered a violation

## How to fix

Given code like this:

```php
namespace Lotr;

use Lotr\Ring;

class Sauron
{
public function forgeTheOneRing()
{
$ring = new Ring('Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul');
$this->putOnFinger($ring);
}
}
```
you would introduce constructor dependency injection with an autogenerated factory like this:

```php
namespace Lotr;

use Lotr\RingFactory;

class Sauron
{
/**
* @var RingFactory
*/
private $ringFactory;

public function __construct(RingFactory $ringFactory)
{
$this->ringFactory = $ringFactory;
}

public function forgeTheOneRing()
{
$ring = $this->ringFactory->create(
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
$this->putOnFinger($ring);
}
}
```

Note that the argument for **create** is always an array where the keys have to equal the constructor argument names for
the class to be instantiated. So the example above assumes a constructor signature for `Ring` like:

```php
public function __construct(string $inscription);
```

If there is an **interface** for `Ring`, e.g. `RingInterface` it is recommended to use a generated factory for the interface, so that the
actual implementation may be exchanged:

```php
public function __construct(RingInterfaceFactory $ringFactory)
```

### Special case: Custom factory

Sometimes, the automatically generated factories do not quite serve our needs and more specific creation methods than
the generic `create` are helpful. You can **create your own factory implementation**, Magento will only generate a
factory class if it cannot find an existing one.

Let's say, we want specific factory methods for different rings, we could implement our own factory class like this:

```php
namespace Lotr;

class RingFactory
{
public function createElvenRing() { ... }
public function createDwarfRing() { ... }
public function createHumanRing() { ... }
public function createTheOneRing() { ... }
}
```

In this factory, we still won't use the `new` keyword, but instead the object manager can be used (**factories are an exception to the rule "Never use the object manager directly"!**)

```php
namespace Lotr;

use Lotr\Ring;

class RingFactory
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;

public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}

public function createTheOneRing(): Ring
{
return $this->objectManager->create(
Ring::class,
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
}
}
```

**Do not use the object manager like this in anything else than factory or builder classes!** In other words, object
creation and object usage must be separated.

The signature of `ObjectManager::create()` is similar to the `create()` method of the automatically generated factories,
but additionally needs a class or interface name as first argument.

With such a custom factory, Sauron can forge the ring and let the "ring factory" decide about the inscription:

```php
public function forgeTheOneRing()
{
$ring = $this->ringFactory->createTheOneRing();
$this->putOnFinger($ring);
}
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Extdn/Samples/Classes/ObjectInstantiation.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Extdn\Samples\Classes;

class ObjectInstantiation
{
public function instantiateSomeObjects()
{
new \DateTime('yesterday');

new stdclass;

$className = 'stdclass';

new $className;

$anonymous = new class implements \Countable {
public function count()
{
return 0;
}
};

$e = new Exception;

throw $e;

throw new Exception;
}
}
4 changes: 4 additions & 0 deletions Extdn/ruleset.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,10 @@
<rule ref="MEQP2.Templates.ThisInTemplate">
<exclude-pattern>*.php</exclude-pattern>
</rule>
<rule ref="MEQP2.Classes.ObjectInstantiation">
<exclude-pattern>*/Test/*</exclude-pattern>
<severity>1</severity>
</rule>
<rule ref="Extdn.Blocks.SetTemplateInBlock"/>
<rule ref="Extdn.Classes.StrictTypes"/>
<rule ref="Extdn.Templates.TemplateObjectManager"/>
Expand Down
183 changes: 183 additions & 0 deletions MEQP2/Sniffs/Classes/ObjectInstantiationSniff.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
# Rule: Keyword `new` should not be used
## Background

The `new` keyword should not be used to instantiate new classes. Constructor DI should be used instead:
Inject a factory (e.g. `ProductInterfaceFactory`) which can be generated automatically, Magento then takes care of
which actual class to instantiate.


**This is an experimental rule, only applied with lowest severity treshold of 1.**

## Reasoning
Magento uses constructor dependency injection with autowiring. It automatically generates interceptors that extend the
original class to add plugins.

It is encouraged to use these mechanisms instead of instantiating objects directly with `new`. This way, Magento can transparently
replace the concrete class with an interceptor or alternative implementations based on DI preference configuration.

This flexibility with plugins and preferences is lost if classes are instantiated directly.

### Exceptions

- Instantiating exceptions with `new` is allowed, there is no need for alternative implementations or plugins in exceptions
- The rule does not apply to unit tests, where automatic constructor DI is not present

### Dispute

**Classes from PHP core or non-Magento libraries:** While it is possible to generate factories for them too, it is
considered overkill by many.

Compare

```php
new DateTime('yesterday')
```
vs
```php
$this->dateTimeFactory->create(['time' => 'yesterday'])
```

**Data transport objects:** Data transport objects (DTOs) are often used in events to allow changing values in observers:

`new \Magento\Framework\DataObject(['key' => 'value])`

Using factories seems unnecessary too here, and Magento does not do it in the core either.

As an alternative, since `DataObject` is not more than a glorified array with magic getters and setters, an `stdclass` object could be used instead, circumventing
the `new` rule: `(object)['key' => 'value']`

**Anonymous classes:** For anonymous classes there is no alternative without `new` and they might be useful as
"private classes", i.e. internal to a module, where plugins are not useful or desired.

## How it works
Any usage of `new` that is not preceded by `throw` is considered a violation

## How to fix

Given code like this:

```php
namespace Lotr;

use Lotr\Ring;

class Sauron
{
public function forgeTheOneRing()
{
$ring = new Ring('Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul');
$this->putOnFinger($ring);
}
}
```
you would introduce constructor dependency injection with an autogenerated factory like this:

```php
namespace Lotr;

use Lotr\RingFactory;

class Sauron
{
/**
* @var RingFactory
*/
private $ringFactory;

public function __construct(RingFactory $ringFactory)
{
$this->ringFactory = $ringFactory;
}

public function forgeTheOneRing()
{
$ring = $this->ringFactory->create(
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
$this->putOnFinger($ring);
}
}
```

Note that the argument for **create** is always an array where the keys have to equal the constructor argument names for
the class to be instantiated. So the example above assumes a constructor signature for `Ring` like:

```php
public function __construct(string $inscription);
```

If there is an **interface** for `Ring`, e.g. `RingInterface` it is recommended to use a generated factory for the interface, so that the
actual implementation may be exchanged:

```php
public function __construct(RingInterfaceFactory $ringFactory)
```

### Special case: Custom factory

Sometimes, the automatically generated factories do not quite serve our needs and more specific creation methods than
the generic `create` are helpful. You can **create your own factory implementation**, Magento will only generate a
factory class if it cannot find an existing one.

Let's say, we want specific factory methods for different rings, we could implement our own factory class like this:

```php
namespace Lotr;

class RingFactory
{
public function createElvenRing() { ... }
public function createDwarfRing() { ... }
public function createHumanRing() { ... }
public function createTheOneRing() { ... }
}
```

In this factory, we still won't use the `new` keyword, but instead the object manager can be used (**factories are an exception to the rule "Never use the object manager directly"!**)

```php
namespace Lotr;

use Lotr\Ring;

class RingFactory
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;

public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}

public function createTheOneRing(): Ring
{
return $this->objectManager->create(
Ring::class,
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
}
}
```

**Do not use the object manager like this in anything else than factory or builder classes!** In other words, object
creation and object usage must be separated.

The signature of `ObjectManager::create()` is similar to the `create()` method of the automatically generated factories,
but additionally needs a class or interface name as first argument.

With such a custom factory, Sauron can forge the ring and let the "ring factory" decide about the inscription:

```php
public function forgeTheOneRing()
{
$ring = $this->ringFactory->createTheOneRing();
$this->putOnFinger($ring);
}
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Extdn/Samples/Classes/ObjectInstantiation.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Extdn\Samples\Classes;

class ObjectInstantiation
{
public function instantiateSomeObjects()
{
new \DateTime('yesterday');

new stdclass;

$className = 'stdclass';

new $className;

$anonymous = new class implements \Countable {
public function count()
{
return 0;
}
};

$e = new Exception;

throw $e;

throw new Exception;
}
}
4 changes: 4 additions & 0 deletions Extdn/ruleset.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,10 @@
<rule ref="MEQP2.Templates.ThisInTemplate">
<exclude-pattern>*.php</exclude-pattern>
</rule>
<rule ref="MEQP2.Classes.ObjectInstantiation">
<exclude-pattern>*/Test/*</exclude-pattern>
<severity>1</severity>
</rule>
<rule ref="Extdn.Blocks.SetTemplateInBlock"/>
<rule ref="Extdn.Classes.StrictTypes"/>
<rule ref="Extdn.Templates.TemplateObjectManager"/>
Expand Down
183 changes: 183 additions & 0 deletions MEQP2/Sniffs/Classes/ObjectInstantiationSniff.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
# Rule: Keyword `new` should not be used
## Background

The `new` keyword should not be used to instantiate new classes. Constructor DI should be used instead:
Inject a factory (e.g. `ProductInterfaceFactory`) which can be generated automatically, Magento then takes care of
which actual class to instantiate.


**This is an experimental rule, only applied with lowest severity treshold of 1.**

## Reasoning
Magento uses constructor dependency injection with autowiring. It automatically generates interceptors that extend the
original class to add plugins.

It is encouraged to use these mechanisms instead of instantiating objects directly with `new`. This way, Magento can transparently
replace the concrete class with an interceptor or alternative implementations based on DI preference configuration.

This flexibility with plugins and preferences is lost if classes are instantiated directly.

### Exceptions

- Instantiating exceptions with `new` is allowed, there is no need for alternative implementations or plugins in exceptions
- The rule does not apply to unit tests, where automatic constructor DI is not present

### Dispute

**Classes from PHP core or non-Magento libraries:** While it is possible to generate factories for them too, it is
considered overkill by many.

Compare

```php
new DateTime('yesterday')
```
vs
```php
$this->dateTimeFactory->create(['time' => 'yesterday'])
```

**Data transport objects:** Data transport objects (DTOs) are often used in events to allow changing values in observers:

`new \Magento\Framework\DataObject(['key' => 'value])`

Using factories seems unnecessary too here, and Magento does not do it in the core either.

As an alternative, since `DataObject` is not more than a glorified array with magic getters and setters, an `stdclass` object could be used instead, circumventing
the `new` rule: `(object)['key' => 'value']`

**Anonymous classes:** For anonymous classes there is no alternative without `new` and they might be useful as
"private classes", i.e. internal to a module, where plugins are not useful or desired.

## How it works
Any usage of `new` that is not preceded by `throw` is considered a violation

## How to fix

Given code like this:

```php
namespace Lotr;

use Lotr\Ring;

class Sauron
{
public function forgeTheOneRing()
{
$ring = new Ring('Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul');
$this->putOnFinger($ring);
}
}
```
you would introduce constructor dependency injection with an autogenerated factory like this:

```php
namespace Lotr;

use Lotr\RingFactory;

class Sauron
{
/**
* @var RingFactory
*/
private $ringFactory;

public function __construct(RingFactory $ringFactory)
{
$this->ringFactory = $ringFactory;
}

public function forgeTheOneRing()
{
$ring = $this->ringFactory->create(
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
$this->putOnFinger($ring);
}
}
```

Note that the argument for **create** is always an array where the keys have to equal the constructor argument names for
the class to be instantiated. So the example above assumes a constructor signature for `Ring` like:

```php
public function __construct(string $inscription);
```

If there is an **interface** for `Ring`, e.g. `RingInterface` it is recommended to use a generated factory for the interface, so that the
actual implementation may be exchanged:

```php
public function __construct(RingInterfaceFactory $ringFactory)
```

### Special case: Custom factory

Sometimes, the automatically generated factories do not quite serve our needs and more specific creation methods than
the generic `create` are helpful. You can **create your own factory implementation**, Magento will only generate a
factory class if it cannot find an existing one.

Let's say, we want specific factory methods for different rings, we could implement our own factory class like this:

```php
namespace Lotr;

class RingFactory
{
public function createElvenRing() { ... }
public function createDwarfRing() { ... }
public function createHumanRing() { ... }
public function createTheOneRing() { ... }
}
```

In this factory, we still won't use the `new` keyword, but instead the object manager can be used (**factories are an exception to the rule "Never use the object manager directly"!**)

```php
namespace Lotr;

use Lotr\Ring;

class RingFactory
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;

public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}

public function createTheOneRing(): Ring
{
return $this->objectManager->create(
Ring::class,
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
}
}
```

**Do not use the object manager like this in anything else than factory or builder classes!** In other words, object
creation and object usage must be separated.

The signature of `ObjectManager::create()` is similar to the `create()` method of the automatically generated factories,
but additionally needs a class or interface name as first argument.

With such a custom factory, Sauron can forge the ring and let the "ring factory" decide about the inscription:

```php
public function forgeTheOneRing()
{
$ring = $this->ringFactory->createTheOneRing();
$this->putOnFinger($ring);
}
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Extdn/Samples/Classes/ObjectInstantiation.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Extdn\Samples\Classes;

class ObjectInstantiation
{
public function instantiateSomeObjects()
{
new \DateTime('yesterday');

new stdclass;

$className = 'stdclass';

new $className;

$anonymous = new class implements \Countable {
public function count()
{
return 0;
}
};

$e = new Exception;

throw $e;

throw new Exception;
}
}
4 changes: 4 additions & 0 deletions Extdn/ruleset.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,10 @@
<rule ref="MEQP2.Templates.ThisInTemplate">
<exclude-pattern>*.php</exclude-pattern>
</rule>
<rule ref="MEQP2.Classes.ObjectInstantiation">
<exclude-pattern>*/Test/*</exclude-pattern>
<severity>1</severity>
</rule>
<rule ref="Extdn.Blocks.SetTemplateInBlock"/>
<rule ref="Extdn.Classes.StrictTypes"/>
<rule ref="Extdn.Templates.TemplateObjectManager"/>
Expand Down
183 changes: 183 additions & 0 deletions MEQP2/Sniffs/Classes/ObjectInstantiationSniff.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
# Rule: Keyword `new` should not be used
## Background

The `new` keyword should not be used to instantiate new classes. Constructor DI should be used instead:
Inject a factory (e.g. `ProductInterfaceFactory`) which can be generated automatically, Magento then takes care of
which actual class to instantiate.


**This is an experimental rule, only applied with lowest severity treshold of 1.**

## Reasoning
Magento uses constructor dependency injection with autowiring. It automatically generates interceptors that extend the
original class to add plugins.

It is encouraged to use these mechanisms instead of instantiating objects directly with `new`. This way, Magento can transparently
replace the concrete class with an interceptor or alternative implementations based on DI preference configuration.

This flexibility with plugins and preferences is lost if classes are instantiated directly.

### Exceptions

- Instantiating exceptions with `new` is allowed, there is no need for alternative implementations or plugins in exceptions
- The rule does not apply to unit tests, where automatic constructor DI is not present

### Dispute

**Classes from PHP core or non-Magento libraries:** While it is possible to generate factories for them too, it is
considered overkill by many.

Compare

```php
new DateTime('yesterday')
```
vs
```php
$this->dateTimeFactory->create(['time' => 'yesterday'])
```

**Data transport objects:** Data transport objects (DTOs) are often used in events to allow changing values in observers:

`new \Magento\Framework\DataObject(['key' => 'value])`

Using factories seems unnecessary too here, and Magento does not do it in the core either.

As an alternative, since `DataObject` is not more than a glorified array with magic getters and setters, an `stdclass` object could be used instead, circumventing
the `new` rule: `(object)['key' => 'value']`

**Anonymous classes:** For anonymous classes there is no alternative without `new` and they might be useful as
"private classes", i.e. internal to a module, where plugins are not useful or desired.

## How it works
Any usage of `new` that is not preceded by `throw` is considered a violation

## How to fix

Given code like this:

```php
namespace Lotr;

use Lotr\Ring;

class Sauron
{
public function forgeTheOneRing()
{
$ring = new Ring('Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul');
$this->putOnFinger($ring);
}
}
```
you would introduce constructor dependency injection with an autogenerated factory like this:

```php
namespace Lotr;

use Lotr\RingFactory;

class Sauron
{
/**
* @var RingFactory
*/
private $ringFactory;

public function __construct(RingFactory $ringFactory)
{
$this->ringFactory = $ringFactory;
}

public function forgeTheOneRing()
{
$ring = $this->ringFactory->create(
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
$this->putOnFinger($ring);
}
}
```

Note that the argument for **create** is always an array where the keys have to equal the constructor argument names for
the class to be instantiated. So the example above assumes a constructor signature for `Ring` like:

```php
public function __construct(string $inscription);
```

If there is an **interface** for `Ring`, e.g. `RingInterface` it is recommended to use a generated factory for the interface, so that the
actual implementation may be exchanged:

```php
public function __construct(RingInterfaceFactory $ringFactory)
```

### Special case: Custom factory

Sometimes, the automatically generated factories do not quite serve our needs and more specific creation methods than
the generic `create` are helpful. You can **create your own factory implementation**, Magento will only generate a
factory class if it cannot find an existing one.

Let's say, we want specific factory methods for different rings, we could implement our own factory class like this:

```php
namespace Lotr;

class RingFactory
{
public function createElvenRing() { ... }
public function createDwarfRing() { ... }
public function createHumanRing() { ... }
public function createTheOneRing() { ... }
}
```

In this factory, we still won't use the `new` keyword, but instead the object manager can be used (**factories are an exception to the rule "Never use the object manager directly"!**)

```php
namespace Lotr;

use Lotr\Ring;

class RingFactory
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;

public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}

public function createTheOneRing(): Ring
{
return $this->objectManager->create(
Ring::class,
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
}
}
```

**Do not use the object manager like this in anything else than factory or builder classes!** In other words, object
creation and object usage must be separated.

The signature of `ObjectManager::create()` is similar to the `create()` method of the automatically generated factories,
but additionally needs a class or interface name as first argument.

With such a custom factory, Sauron can forge the ring and let the "ring factory" decide about the inscription:

```php
public function forgeTheOneRing()
{
$ring = $this->ringFactory->createTheOneRing();
$this->putOnFinger($ring);
}
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Extdn/Samples/Classes/ObjectInstantiation.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Extdn\Samples\Classes;

class ObjectInstantiation
{
public function instantiateSomeObjects()
{
new \DateTime('yesterday');

new stdclass;

$className = 'stdclass';

new $className;

$anonymous = new class implements \Countable {
public function count()
{
return 0;
}
};

$e = new Exception;

throw $e;

throw new Exception;
}
}
4 changes: 4 additions & 0 deletions Extdn/ruleset.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,10 @@
<rule ref="MEQP2.Templates.ThisInTemplate">
<exclude-pattern>*.php</exclude-pattern>
</rule>
<rule ref="MEQP2.Classes.ObjectInstantiation">
<exclude-pattern>*/Test/*</exclude-pattern>
<severity>1</severity>
</rule>
<rule ref="Extdn.Blocks.SetTemplateInBlock"/>
<rule ref="Extdn.Classes.StrictTypes"/>
<rule ref="Extdn.Templates.TemplateObjectManager"/>
Expand Down
183 changes: 183 additions & 0 deletions MEQP2/Sniffs/Classes/ObjectInstantiationSniff.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
# Rule: Keyword `new` should not be used
## Background

The `new` keyword should not be used to instantiate new classes. Constructor DI should be used instead:
Inject a factory (e.g. `ProductInterfaceFactory`) which can be generated automatically, Magento then takes care of
which actual class to instantiate.


**This is an experimental rule, only applied with lowest severity treshold of 1.**

## Reasoning
Magento uses constructor dependency injection with autowiring. It automatically generates interceptors that extend the
original class to add plugins.

It is encouraged to use these mechanisms instead of instantiating objects directly with `new`. This way, Magento can transparently
replace the concrete class with an interceptor or alternative implementations based on DI preference configuration.

This flexibility with plugins and preferences is lost if classes are instantiated directly.

### Exceptions

- Instantiating exceptions with `new` is allowed, there is no need for alternative implementations or plugins in exceptions
- The rule does not apply to unit tests, where automatic constructor DI is not present

### Dispute

**Classes from PHP core or non-Magento libraries:** While it is possible to generate factories for them too, it is
considered overkill by many.

Compare

```php
new DateTime('yesterday')
```
vs
```php
$this->dateTimeFactory->create(['time' => 'yesterday'])
```

**Data transport objects:** Data transport objects (DTOs) are often used in events to allow changing values in observers:

`new \Magento\Framework\DataObject(['key' => 'value])`

Using factories seems unnecessary too here, and Magento does not do it in the core either.

As an alternative, since `DataObject` is not more than a glorified array with magic getters and setters, an `stdclass` object could be used instead, circumventing
the `new` rule: `(object)['key' => 'value']`

**Anonymous classes:** For anonymous classes there is no alternative without `new` and they might be useful as
"private classes", i.e. internal to a module, where plugins are not useful or desired.

## How it works
Any usage of `new` that is not preceded by `throw` is considered a violation

## How to fix

Given code like this:

```php
namespace Lotr;

use Lotr\Ring;

class Sauron
{
public function forgeTheOneRing()
{
$ring = new Ring('Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul');
$this->putOnFinger($ring);
}
}
```
you would introduce constructor dependency injection with an autogenerated factory like this:

```php
namespace Lotr;

use Lotr\RingFactory;

class Sauron
{
/**
* @var RingFactory
*/
private $ringFactory;

public function __construct(RingFactory $ringFactory)
{
$this->ringFactory = $ringFactory;
}

public function forgeTheOneRing()
{
$ring = $this->ringFactory->create(
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
$this->putOnFinger($ring);
}
}
```

Note that the argument for **create** is always an array where the keys have to equal the constructor argument names for
the class to be instantiated. So the example above assumes a constructor signature for `Ring` like:

```php
public function __construct(string $inscription);
```

If there is an **interface** for `Ring`, e.g. `RingInterface` it is recommended to use a generated factory for the interface, so that the
actual implementation may be exchanged:

```php
public function __construct(RingInterfaceFactory $ringFactory)
```

### Special case: Custom factory

Sometimes, the automatically generated factories do not quite serve our needs and more specific creation methods than
the generic `create` are helpful. You can **create your own factory implementation**, Magento will only generate a
factory class if it cannot find an existing one.

Let's say, we want specific factory methods for different rings, we could implement our own factory class like this:

```php
namespace Lotr;

class RingFactory
{
public function createElvenRing() { ... }
public function createDwarfRing() { ... }
public function createHumanRing() { ... }
public function createTheOneRing() { ... }
}
```

In this factory, we still won't use the `new` keyword, but instead the object manager can be used (**factories are an exception to the rule "Never use the object manager directly"!**)

```php
namespace Lotr;

use Lotr\Ring;

class RingFactory
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;

public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}

public function createTheOneRing(): Ring
{
return $this->objectManager->create(
Ring::class,
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
}
}
```

**Do not use the object manager like this in anything else than factory or builder classes!** In other words, object
creation and object usage must be separated.

The signature of `ObjectManager::create()` is similar to the `create()` method of the automatically generated factories,
but additionally needs a class or interface name as first argument.

With such a custom factory, Sauron can forge the ring and let the "ring factory" decide about the inscription:

```php
public function forgeTheOneRing()
{
$ring = $this->ringFactory->createTheOneRing();
$this->putOnFinger($ring);
}
```
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Extdn/Samples/Classes/ObjectInstantiation.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace Extdn\Samples\Classes;

class ObjectInstantiation
{
public function instantiateSomeObjects()
{
new \DateTime('yesterday');

new stdclass;

$className = 'stdclass';

new $className;

$anonymous = new class implements \Countable {
public function count()
{
return 0;
}
};

$e = new Exception;

throw $e;

throw new Exception;
}
}
4 changes: 4 additions & 0 deletions Extdn/ruleset.xml
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,10 @@
<rule ref="MEQP2.Templates.ThisInTemplate">
<exclude-pattern>*.php</exclude-pattern>
</rule>
<rule ref="MEQP2.Classes.ObjectInstantiation">
<exclude-pattern>*/Test/*</exclude-pattern>
<severity>1</severity>
</rule>
<rule ref="Extdn.Blocks.SetTemplateInBlock"/>
<rule ref="Extdn.Classes.StrictTypes"/>
<rule ref="Extdn.Templates.TemplateObjectManager"/>
Expand Down
183 changes: 183 additions & 0 deletions MEQP2/Sniffs/Classes/ObjectInstantiationSniff.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
# Rule: Keyword `new` should not be used
## Background

The `new` keyword should not be used to instantiate new classes. Constructor DI should be used instead:
Inject a factory (e.g. `ProductInterfaceFactory`) which can be generated automatically, Magento then takes care of
which actual class to instantiate.


**This is an experimental rule, only applied with lowest severity treshold of 1.**

## Reasoning
Magento uses constructor dependency injection with autowiring. It automatically generates interceptors that extend the
original class to add plugins.

It is encouraged to use these mechanisms instead of instantiating objects directly with `new`. This way, Magento can transparently
replace the concrete class with an interceptor or alternative implementations based on DI preference configuration.

This flexibility with plugins and preferences is lost if classes are instantiated directly.

### Exceptions

- Instantiating exceptions with `new` is allowed, there is no need for alternative implementations or plugins in exceptions
- The rule does not apply to unit tests, where automatic constructor DI is not present

### Dispute

**Classes from PHP core or non-Magento libraries:** While it is possible to generate factories for them too, it is
considered overkill by many.

Compare

```php
new DateTime('yesterday')
```
vs
```php
$this->dateTimeFactory->create(['time' => 'yesterday'])
```

**Data transport objects:** Data transport objects (DTOs) are often used in events to allow changing values in observers:

`new \Magento\Framework\DataObject(['key' => 'value])`

Using factories seems unnecessary too here, and Magento does not do it in the core either.

As an alternative, since `DataObject` is not more than a glorified array with magic getters and setters, an `stdclass` object could be used instead, circumventing
the `new` rule: `(object)['key' => 'value']`

**Anonymous classes:** For anonymous classes there is no alternative without `new` and they might be useful as
"private classes", i.e. internal to a module, where plugins are not useful or desired.

## How it works
Any usage of `new` that is not preceded by `throw` is considered a violation

## How to fix

Given code like this:

```php
namespace Lotr;

use Lotr\Ring;

class Sauron
{
public function forgeTheOneRing()
{
$ring = new Ring('Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul');
$this->putOnFinger($ring);
}
}
```
you would introduce constructor dependency injection with an autogenerated factory like this:

```php
namespace Lotr;

use Lotr\RingFactory;

class Sauron
{
/**
* @var RingFactory
*/
private $ringFactory;

public function __construct(RingFactory $ringFactory)
{
$this->ringFactory = $ringFactory;
}

public function forgeTheOneRing()
{
$ring = $this->ringFactory->create(
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
$this->putOnFinger($ring);
}
}
```

Note that the argument for **create** is always an array where the keys have to equal the constructor argument names for
the class to be instantiated. So the example above assumes a constructor signature for `Ring` like:

```php
public function __construct(string $inscription);
```

If there is an **interface** for `Ring`, e.g. `RingInterface` it is recommended to use a generated factory for the interface, so that the
actual implementation may be exchanged:

```php
public function __construct(RingInterfaceFactory $ringFactory)
```

### Special case: Custom factory

Sometimes, the automatically generated factories do not quite serve our needs and more specific creation methods than
the generic `create` are helpful. You can **create your own factory implementation**, Magento will only generate a
factory class if it cannot find an existing one.

Let's say, we want specific factory methods for different rings, we could implement our own factory class like this:

```php
namespace Lotr;

class RingFactory
{
public function createElvenRing() { ... }
public function createDwarfRing() { ... }
public function createHumanRing() { ... }
public function createTheOneRing() { ... }
}
```

In this factory, we still won't use the `new` keyword, but instead the object manager can be used (**factories are an exception to the rule "Never use the object manager directly"!**)

```php
namespace Lotr;

use Lotr\Ring;

class RingFactory
{
/**
* @var \Magento\Framework\ObjectManagerInterface
*/
private $objectManager;

public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager)
{
$this->objectManager = $objectManager;
}

public function createTheOneRing(): Ring
{
return $this->objectManager->create(
Ring::class,
[
'inscription' => 'Ash nazg durbatulûk, ash nazg gimbatul, ash nazg thrakatulûk, agh burzum-ishi krimpatul'
]
);
}
}
```

**Do not use the object manager like this in anything else than factory or builder classes!** In other words, object
creation and object usage must be separated.

The signature of `ObjectManager::create()` is similar to the `create()` method of the automatically generated factories,
but additionally needs a class or interface name as first argument.

With such a custom factory, Sauron can forge the ring and let the "ring factory" decide about the inscription:

```php
public function forgeTheOneRing()
{
$ring = $this->ringFactory->createTheOneRing();
$this->putOnFinger($ring);
}
```