Completed
Pull Request — 1.0 (#13)
by Raphaël
05:44
created

EntitiesGeneratorService::generateAll()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 18
rs 9.4285
cc 3
eloc 12
nc 3
nop 2
1
<?php
2
3
namespace Wabel\Zoho\CRM\Service;
4
5
use gossi\codegen\generator\CodeFileGenerator;
6
use gossi\codegen\model\PhpClass;
7
use gossi\codegen\model\PhpMethod;
8
use gossi\codegen\model\PhpParameter;
9
use gossi\codegen\model\PhpProperty;
10
use Psr\Log\LoggerInterface;
11
use Wabel\Zoho\CRM\ZohoClient;
12
use Wabel\Zoho\CRM\Exception\ZohoCRMException;
13
14
/**
15
 * This class is in charge of generating Zoho entities.
16
 */
17
class EntitiesGeneratorService
18
{
19
    private $zohoClient;
20
    private $logger;
21
22
    public function __construct(ZohoClient $zohoClient, LoggerInterface $logger)
23
    {
24
        $this->zohoClient = $zohoClient;
25
        $this->logger = $logger;
26
    }
27
28
    /**
29
     * Generate ALL entities for all Zoho modules.
30
     *
31
     * @param string $targetDirectory
32
     * @param string $namespace
33
     * @return array Array about each fully qualified dao namespace
34
     */
35
    public function generateAll($targetDirectory, $namespace)
36
    {
37
        $modules = $this->zohoClient->getModules();
38
        $zohoModules = [];
39
        foreach ($modules->getRecords() as $module) {
40
            try {
41
                $zohoModules[] = $this->generateModule($module['key'], $module['pl'], $module['sl'], $targetDirectory, $namespace);
42
            } catch (ZohoCRMException $e) {
43
                $this->logger->notice('Error thrown when retrieving fields for module {module}. Error message: {error}.',
44
                    [
45
                        'module' => $module['key'],
46
                        'error' => $e->getMessage(),
47
                        'exception' => $e,
48
                    ]);
49
            }
50
        }
51
        return $zohoModules;
52
    }
53
54
    /**
55
     * Generate a dao for a zoho module
56
     * @param string $moduleName
57
     * @param string $modulePlural
58
     * @param string $moduleSingular
59
     * @param string $targetDirectory
60
     * @param string $namespace
61
     * @return string The fully qualified Dao namespace
62
     */
63
    public function generateModule($moduleName, $modulePlural, $moduleSingular, $targetDirectory, $namespace)
64
    {
65
        $fields = $this->zohoClient->getFields($moduleName);
66
67
        if (!file_exists($targetDirectory)) {
68
            mkdir($targetDirectory, 0775, true);
69
        }
70
71
        $namespace = trim($namespace, '\\');
72
        $className = self::upperCamelCase($moduleSingular);
73
        $daoClassName = $className.'ZohoDao';
74
75
        $fieldRecords = $fields->getRecords();
76
77
        $this->generateBean($fieldRecords, $namespace, $className, $moduleName, $targetDirectory);
78
        $this->generateDao($fieldRecords, $namespace, $className, $daoClassName, $moduleName, $targetDirectory, $moduleSingular, $modulePlural);
79
80
        return $namespace.'\\'.$daoClassName;
81
    }
82
83
    public function generateBean($fields, $namespace, $className, $moduleName, $targetDirectory)
84
    {
85
86
//        if (class_exists($namespace."\\".$className)) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
87
//            $class = PhpClass::fromReflection(new \ReflectionClass($namespace."\\".$className));
88
//        } else {
89
            $class = PhpClass::create();
90
//        }
91
92
        $class->setName($className)
93
            ->setNamespace($namespace)
94
            ->addInterface('\\Wabel\\Zoho\\CRM\\ZohoBeanInterface')
95
            ->setMethod(PhpMethod::create('__construct'));
96
97
        // Let's add the ZohoID property
98
        self::registerProperty($class, 'zohoId', "The ID of this record in Zoho\nType: string\n", 'string');
99
100
        $usedIdentifiers = [];
101
102
        foreach ($fields as &$fieldCategory) {
103
            foreach ($fieldCategory as $name => &$field) {
104
                $req = $field['req'];
0 ignored issues
show
Unused Code introduced by
$req is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
105
                $type = $field['type'];
106
                $isreadonly = $field['isreadonly'];
107
                $maxlength = $field['maxlength'];
108
                $label = $field['label'];
0 ignored issues
show
Unused Code introduced by
$label is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
109
                $dv = $field['dv'];
0 ignored issues
show
Unused Code introduced by
$dv is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
110
                $customfield = $field['customfield'];
111
112 View Code Duplication
                switch ($type) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
113
                    case 'DateTime':
114
                    case 'Date':
115
                        $phpType = '\\DateTime';
116
                        break;
117
                    case 'Boolean':
118
                        $phpType = 'bool';
119
                        break;
120
                    case 'Integer':
121
                        $phpType = 'int';
122
                        break;
123
                    default:
124
                        $phpType = 'string';
125
                        break;
126
                }
127
128
                $field['phpType'] = $phpType;
129
130
                $identifier = $this->getUniqueIdentifier($name, $usedIdentifiers);
131
                $usedIdentifiers[$identifier] = true;
132
133
                self::registerProperty($class, $identifier, 'Zoho field '.$name."\n".
134
                    'Type: '.$type."\n".
135
                    'Read only: '.($isreadonly ? 'true' : 'false')."\n".
136
                    'Max length: '.$maxlength."\n".
137
                    'Custom field: '.($customfield ? 'true' : 'false')."\n", $phpType);
138
139
                // Adds a ID field for lookups
140
                if ($type === 'Lookup') {
141
                    $generateId = false;
142
143
                    if ($customfield) {
144
                        $name .= '_ID';
145
                        $generateId = true;
146
                    } else {
147
                        switch ($name) {
148
                            //TODO : To be completed with known lookup fields that are not custom fields but default in Zoho
149
                            case 'Account Name' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
150
                                $name = 'ACCOUNTID';
151
                                $generateId = true;
152
                                break;
153
                            case 'Contact Name' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
154
                                $name = 'CONTACTID';
155
                                $generateId = true;
156
                                break;
157
                            default :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a DEFAULT statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in the default statement.

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
158
                                $this->logger->warning('Unable to set a ID for the field {name} of the {module} module', [
159
                                    'name' => $name,
160
                                    'module' => $moduleName,
161
                                ]);
162
                        }
163
                    }
164
165
                    if ($generateId) {
166
                        $req = false;
0 ignored issues
show
Unused Code introduced by
$req is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
167
                        $type = 'Lookup ID';
168
                        $isreadonly = true;
169
                        $maxlength = $field['maxlength'];
170
                        $label = $field['label'];
0 ignored issues
show
Unused Code introduced by
$label is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
171
                        $dv = $field['dv'];
0 ignored issues
show
Unused Code introduced by
$dv is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
172
173
                        $field['phpType'] = $phpType;
174
175
                        self::registerProperty($class, ($customfield ? self::camelCase($name) : $name), 'Zoho field '.$name."\n".
176
                            'Type: '.$type."\n".
177
                            'Read only: '.($isreadonly ? 'true' : 'false')."\n".
178
                            'Max length: '.$maxlength."\n".
179
                            'Custom field: '.($customfield ? 'true' : 'false')."\n", 'string');
180
                    }
181
                }
182
            }
183
        }
184
185
        self::registerProperty($class, 'createdTime', "The time the record was created in Zoho\nType: DateTime\n", '\\DateTime');
186
        self::registerProperty($class, 'modifiedTime', "The last time the record was modified in Zoho\nType: DateTime\n", '\\DateTime');
187
188
        $method = PhpMethod::create('isDirty');
189
        $method->setDescription('Returns whether a property is changed or not.');
190
        $method->addParameter(PhpParameter::create('name'));
191
        $method->setBody("\$propertyName = 'dirty'.ucfirst(\$name);\nreturn \$this->\$propertyName;");
192
        $method->setType('bool');
193
        $class->setMethod($method);
194
195
        $generator = new CodeFileGenerator();
196
        $code = $generator->generate($class);
197
198 View Code Duplication
        if (!file_put_contents(rtrim($targetDirectory, '/').'/'.$className.'.php', $code)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
199
            throw new ZohoCRMException("An error occurred while creating the class $className. Please verify the target directory or the rights of the file.");
200
        }
201
    }
202
203
    /**
204
     * Returns a unique identifier from the name.
205
     *
206
     * @param $name
207
     * @param array $usedNames
0 ignored issues
show
Bug introduced by
There is no parameter named $usedNames. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
208
     */
209
    private function getUniqueIdentifier($name, array $usedIdentifiers) {
210
        $id = self::camelCase($name);
211
        if (isset($usedIdentifiers[$id])) {
212
            $counter = 2;
213
            while (isset($usedIdentifiers[$id.'_'.$counter])) {
214
                $counter++;
215
            }
216
            return $id.'_'.$counter;
217
        } else {
218
            return $id;
219
        }
220
    }
221
222
    public function generateDao($fields, $namespace, $className, $daoClassName, $moduleName, $targetDirectory, $moduleSingular, $modulePlural)
223
    {
224
        //        if (class_exists($namespace."\\".$className)) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
225
//            $class = PhpClass::fromReflection(new \ReflectionClass($namespace."\\".$daoClassName));
226
//        } else {
227
            $class = PhpClass::create();
228
//        }
229
230
        $class->setName($daoClassName)
231
            ->setNamespace($namespace)
232
            ->setParentClassName('\\Wabel\\Zoho\\CRM\\AbstractZohoDao');
233
234
        $usedIdentifiers = [];
235
236
        foreach ($fields as $key => $fieldCategory) {
237
            foreach ($fieldCategory as $name => $field) {
238
                $type = $field['type'];
239
240 View Code Duplication
                switch ($type) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
241
                    case 'DateTime':
242
                    case 'Date':
243
                        $phpType = '\\DateTime';
244
                        break;
245
                    case 'Boolean':
246
                        $phpType = 'bool';
247
                        break;
248
                    case 'Integer':
249
                        $phpType = 'int';
250
                        break;
251
                    default:
252
                        $phpType = 'string';
253
                        break;
254
                }
255
256
                $fields[$key][$name]['phpType'] = $phpType;
257
                $identifier = $this->getUniqueIdentifier($name, $usedIdentifiers);
258
                $usedIdentifiers[$identifier] = true;
259
                $fields[$key][$name]['getter'] = 'get'.ucfirst($identifier);
260
                $fields[$key][$name]['setter'] = 'set'.ucfirst($identifier);
261
                $fields[$key][$name]['name'] = $identifier;
262
263
                if ($type === 'Lookup') {
264
                    $generateId = false;
265
266
                    if ($field['customfield']) {
267
                        $name .= '_ID';
268
                        $generateId = true;
269
                    } else {
270
                        switch ($field['label']) {
271
                            //TODO : To be completed with known lookup fields that are not custom fields but default in Zoho
272
                            case 'Account Name' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
273
                                $name = 'ACCOUNTID';
274
                                $generateId = true;
275
                                break;
276
                            case 'Contact Name' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
277
                                $name = 'CONTACTID';
278
                                $generateId = true;
279
                                break;
280
                        }
281
                    }
282
                    if ($generateId) {
283
                        $fields[$key][$name]['req'] = false;
284
                        $fields[$key][$name]['type'] = 'Lookup ID';
285
                        $fields[$key][$name]['isreadonly'] = true;
286
                        $fields[$key][$name]['maxlength'] = 100;
287
                        $fields[$key][$name]['label'] = $name;
288
                        $fields[$key][$name]['dv'] = $name;
289
                        $fields[$key][$name]['customfield'] = true;
290
                        $fields[$key][$name]['phpType'] = $phpType;
291
                        $fields[$key][$name]['getter'] = 'get'.ucfirst(self::camelCase($name));
292
                        $fields[$key][$name]['setter'] = 'set'.ucfirst(self::camelCase($name));
293
                        $fields[$key][$name]['name'] = self::camelCase($name);
294
                    }
295
                }
296
            }
297
        }
298
299
        $class->setMethod(PhpMethod::create('getModule')->setBody('return '.var_export($moduleName, true).';'));
300
301
        $class->setMethod(PhpMethod::create('getSingularModuleName')->setBody('return '.var_export($moduleSingular, true).';'));
302
303
        $class->setMethod(PhpMethod::create('getPluralModuleName')->setBody('return '.var_export($modulePlural, true).';'));
304
305
        $class->setMethod(PhpMethod::create('getFields')->setBody('return '.var_export($fields, true).';'));
306
307
        $class->setMethod(PhpMethod::create('getBeanClassName')->setBody('return '.var_export($namespace.'\\'.$className, true).';'));
308
309
        $generator = new CodeFileGenerator();
310
        $code = $generator->generate($class);
311
312 View Code Duplication
        if (!file_put_contents(rtrim($targetDirectory, '/').'/'.$daoClassName.'.php', $code)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
313
            throw new ZohoCRMException("An error occurred while creating the DAO $daoClassName. Please verify the target directory exists or the rights of the file.");
314
        }
315
    }
316
317
    private static function camelCase($str, array $noStrip = [])
318
    {
319
        $str = self::upperCamelCase($str, $noStrip);
320
        $str = lcfirst($str);
321
322
        return $str;
323
    }
324
325
    private static function upperCamelCase($str, array $noStrip = [])
326
    {
327
        // non-alpha and non-numeric characters become spaces
328
        $str = preg_replace('/[^a-z0-9'.implode('', $noStrip).']+/i', ' ', $str);
329
        $str = trim($str);
330
        // uppercase the first character of each word
331
        $str = ucwords($str);
332
        $str = str_replace(' ', '', $str);
333
334
        return $str;
335
    }
336
337
    private static function registerProperty(PhpClass $class, $name, $description, $type)
338
    {
339 View Code Duplication
        if (!$class->hasProperty($name)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
340
            $property = PhpProperty::create($name);
341
            $property->setDescription($description);
342
            $property->setType($type);
343
            $property->setVisibility('protected');
344
345
            $class->setProperty($property);
346
        }
347
348
        $isDirtyName = 'dirty'.ucfirst($name);
349 View Code Duplication
        if (!$class->hasProperty($isDirtyName)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
350
            $dirtyProperty = PhpProperty::create($isDirtyName);
351
            $dirtyProperty->setDescription("Whether '$name' has been changed or not.");
352
            $dirtyProperty->setType('bool');
353
            $dirtyProperty->setVisibility('protected');
354
            $dirtyProperty->setDefaultValue(false);
0 ignored issues
show
Deprecated Code introduced by
The method gossi\codegen\model\part...Part::setDefaultValue() has been deprecated with message: use `setValue()` instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
355
356
            $class->setProperty($dirtyProperty);
357
        }
358
359
        $getterName = 'get'.ucfirst($name);
360
        $getterDescription = 'Get '.lcfirst($description);
361
        $setterName = 'set'.ucfirst($name);
362
        $setterDescription = 'Set '.lcfirst($description);
363
364
        if (!$class->hasMethod($getterName)) {
365
            $method = PhpMethod::create($getterName);
366
            $method->setDescription($getterDescription);
367
            $method->setBody("return \$this->{$name};");
368
            $class->setMethod($method);
369
        }
370
371
        if (!$class->hasMethod($setterName)) {
372
            $method = PhpMethod::create($setterName);
373
            $method->setDescription($setterDescription);
374
            $method->addParameter(PhpParameter::create($name)->setType($type));
375
            $method->setBody("\$this->{$name} = \${$name};\n".
376
                             "\$this->dirty".ucfirst($name)." = true;\n".
377
                             "return \$this;");
378
            $class->setMethod($method);
379
        }
380
    }
381
382
    public function getZohoClient()
383
    {
384
        return $this->zohoClient;
385
    }
386
    
387
}
388