-
-
Notifications
You must be signed in to change notification settings - Fork 136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Doctrine Inflector 2.0 #90
Conversation
lib/Doctrine/Inflector/Inflect.php
Outdated
*/ | ||
public function addRules(string $type, array $rules, bool $reset = false) : void | ||
{ | ||
switch ($type) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having this, why don't we just have addSingularRule(s)
and addPluralRule(s)
?
lib/Doctrine/Inflector/Inflect.php
Outdated
return; | ||
} | ||
|
||
throw new InvalidArgumentException(sprintf('Invalid rule type specified "%s"', $type)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would need to be a package exception: Doctrine\Inflector\Exception\InvalidType
.
lib/Doctrine/Inflector/Inflect.php
Outdated
*/ | ||
public function tableize(string $word) : string | ||
{ | ||
return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't unicode safe, needs to be mb_strtolower and use u
regex modifier.
|
||
namespace Doctrine\Inflector; | ||
|
||
interface InflectInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should not be suffixed with Interface
and class name imho shouldn't be a verb (what's wrong with Inflector
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now I have left the old Inflector class with the static methods and have maintained BC with the old interface proxying to the new one. Once I have the tests moved over to the new API I will remove the existing Inflector class and rename things.
* Clears Inflectors inflected value caches, and resets the inflection | ||
* rules to the initial values. | ||
*/ | ||
public function reset() : void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should imho be removed. Resetting should be done by creating new instance. (Same for add*
methods btw.)
* ### Usage: | ||
* | ||
* {{{ | ||
* Inflector::rules('plural', ['/^(inflect)or$/i' => '\1ables']); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs to be updated.
use function array_merge; | ||
use function is_array; | ||
|
||
abstract class InflectorService |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really convinced about having this thing here. :/
|
||
return $this->cache['pluralize'][$word]; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As Scrutinizer points out, this will implicitly return null
which is incompatible with non-null return type.
* | ||
* @var string[] | ||
*/ | ||
private $uninflected = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const?
|
||
abstract class InflectorService | ||
{ | ||
/** @var mixed[] */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we do this better? Splitting into smaller units maybe?
7cf9038
to
8192c8a
Compare
f202eeb
to
4a0fe1b
Compare
@@ -18,18 +18,19 @@ | |||
"require-dev": { | |||
"doctrine/coding-standard": "^4.0", | |||
"phpstan/phpstan": "^0.9.2", | |||
"phpstan/phpstan-phpunit": "^0.9.4", |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
/** | ||
* Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'. | ||
*/ | ||
public function tableize(string $word) : string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that tableize() + classify() + camelize() + ucwords() isn't really inflecting, should we add it to the Inflector itself? Maybe separate class(es) would be better, especially once we introduce an interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure honestly. The definition of inflection is broad enough to include the tableize, classify, etc. methods. If you follow that logic, you'd have a different class for each method. That is a pain for the end user to have to have multiple classes/services.
lib/Doctrine/Inflector/Inflector.php
Outdated
* | ||
* @return string The string with all delimiter-separated words capitalized. | ||
*/ | ||
public function ucwords(string $string, string $delimiters = " \n\t\r\0\x0B-") : string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any idea for a better name? How about capitalize()?
lib/Doctrine/Inflector/Inflector.php
Outdated
class Inflector | ||
{ | ||
/** @var WordInflector */ | ||
private $singularInflector; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just name it $singularizer
.
lib/Doctrine/Inflector/Inflector.php
Outdated
private $singularInflector; | ||
|
||
/** @var WordInflector */ | ||
private $pluralInflector; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's just name it $pluralizer
.
use Doctrine\Inflector\Rules\Singular; | ||
use Doctrine\Inflector\Rules\Uninflected; | ||
|
||
class InflectorFactory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still don't like something on this class. Probably all those nullable parameters.
How about splitting this into three fluent (immutable) factories instead?
(new SingularizerFactory())
->withRules($customRules)
->withIrregular($irregular)
->build(); // returns RulesetInflector
Those not overriden with with*()
would use defaults during build()
.
Same for createPluralRuleset()
and createInflector()
.
I can provide PR for this later separately (so we don't complicate thing too much in this PR.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue recorded here #94
public function inflect(string $word) : string | ||
{ | ||
foreach ($this->rules as $rule) { | ||
if (preg_match($rule->getFrom(), $word)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
must use !== 0
private function getRegex() : string | ||
{ | ||
if ($this->regex === null) { | ||
$words = array_map(function (Word $word) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return type missing
return $word->getWord(); | ||
}, $this->words); | ||
|
||
$this->regex = '(?:' . implode('|', $words) . ')'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible regexp injection here, each item in words
must be properly escaped by preg_quote()
with correct delimiter set as 2nd argument (possibly in the array_map()
above).
return $inflected; | ||
} | ||
|
||
if ($this->ruleset->getUninflected()->isUninflected($word)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Imho this check should be prioritized before attempting to inflect an irregular.
class Ruleset | ||
{ | ||
/** @var Rules */ | ||
private $rules; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to call this $regular
(opposed to $irregular
) instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's finish the rest in follow-ups.
What's the upgrade path from 1.x to 2.0? Will there be a 1.x version which will help users migrate to 2.0? |
@teohhanhui The upgrade path is like this:
Would be changed to this:
So basically we're just not using static methods anymore and you have to instantiate the Inflector. You can see the 2.0 docs here https://www.doctrine-project.org/projects/doctrine-inflector/en/2.0/ |
😉 |
That seems really verbose! Would you consider adding a static factory method to get the default inflector? |
@garrettw I should clarify, we're not using static methods for things that don't make sense to be static. Returning a static set of rules is a normal use case for a static method. @teohhanhui You would do that setup somewhere in your dependency injection container/layer and then you would just get the inflector from your container and inject it to the places that you need it. |
@jwage relax, I'm just messing with you 😉 But in all seriousness, it is pretty verbose. I do understand the whole DIC pattern but I could still see a small factory being useful in some cases. |
Refactor everything.