Skip to content
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

Merged
merged 1 commit into from
Jun 15, 2018
Merged

Doctrine Inflector 2.0 #90

merged 1 commit into from
Jun 15, 2018

Conversation

jwage
Copy link
Member

@jwage jwage commented Jun 8, 2018

Refactor everything.

@jwage jwage added this to the 2.0 milestone Jun 8, 2018
@jwage jwage self-assigned this Jun 8, 2018
@jwage jwage changed the title Move Doctrine\Common\Inflector to Doctrine\Inflector. Doctrine Inflector 2.0 Jun 8, 2018
*/
public function addRules(string $type, array $rules, bool $reset = false) : void
{
switch ($type) {
Copy link
Contributor

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)?

return;
}

throw new InvalidArgumentException(sprintf('Invalid rule type specified "%s"', $type));
Copy link
Contributor

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.

*/
public function tableize(string $word) : string
{
return strtolower(preg_replace('~(?<=\\w)([A-Z])~', '_$1', $word));
Copy link
Contributor

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
Copy link
Contributor

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?

Copy link
Member Author

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;
Copy link
Contributor

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']);
Copy link
Contributor

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
Copy link
Contributor

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];
}
}
Copy link
Contributor

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 = [
Copy link
Contributor

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[] */
Copy link
Contributor

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?

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

/**
* Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'.
*/
public function tableize(string $word) : string
Copy link
Contributor

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.

Copy link
Member Author

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.

*
* @return string The string with all delimiter-separated words capitalized.
*/
public function ucwords(string $string, string $delimiters = " \n\t\r\0\x0B-") : string
Copy link
Contributor

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()?

class Inflector
{
/** @var WordInflector */
private $singularInflector;
Copy link
Contributor

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.

private $singularInflector;

/** @var WordInflector */
private $pluralInflector;
Copy link
Contributor

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
Copy link
Contributor

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

Copy link
Member Author

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)) {
Copy link
Contributor

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) {
Copy link
Contributor

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) . ')';
Copy link
Contributor

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)) {
Copy link
Contributor

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;
Copy link
Contributor

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?

Copy link
Contributor

@Majkl578 Majkl578 left a 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.

@jwage jwage merged commit f62dcf4 into master Jun 15, 2018
@jwage jwage deleted the inflector2.0 branch June 15, 2018 18:37
@teohhanhui
Copy link

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?

@jwage
Copy link
Member Author

jwage commented Jul 18, 2018

@teohhanhui The upgrade path is like this:

$plural = Inflector::pluralize('base');

Would be changed to this:

$inflector = new Inflector(
    new CachedWordInflector(new RulesetInflector(
        English\Rules::getSingularRuleset()
    )),
    new CachedWordInflector(new RulesetInflector(
        English\Rules::getPluralRuleset()
    ))
);
$plural = $inflector->pluralize('base');

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/

@garrettw
Copy link

garrettw commented Jul 18, 2018

not using static methods

English\Rules::getSingularRuleset()

😉

@teohhanhui
Copy link

teohhanhui commented Jul 18, 2018

That seems really verbose! Would you consider adding a static factory method to get the default inflector?

@jwage
Copy link
Member Author

jwage commented Jul 18, 2018

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

@garrettw
Copy link

garrettw commented Jul 18, 2018

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

@alcaeus alcaeus modified the milestones: 2.0.0, 1.4.0 May 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants