Completed
Push — master ( da6ff3...4f4aae )
by Vitaly
02:49
created

CMS::rewriteEntityLocale()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 10
rs 9.4285
ccs 0
cts 7
cp 0
cc 3
eloc 4
nc 3
nop 0
crap 12
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 29 and the first side effect is on line 5.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
namespace samsoncms\api;
3
4
// Backward compatibility
5 1
require('generated/Material.php');
6 1
require('generated/Field.php');
7 1
require('generated/MaterialField.php');
8 1
require('generated/Structure.php');
9 1
require('generated/StructureField.php');
10
11
use samsoncms\api\generator\Collection;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, samsoncms\api\Collection.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
12
use samsoncms\api\generator\Entity;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, samsoncms\api\Entity.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
13
use samsoncms\api\generator\Metadata;
14
use samsoncms\api\generator\Query;
15
use samsoncms\api\generator\Analyzer;
16
17
use samsoncms\application\GeneratorApplication;
18
use samsonframework\core\ResourcesInterface;
19
use samsonframework\core\SystemInterface;
20
use samson\activerecord\TableRelation;
21
use samson\core\CompressableService;
22
use samson\activerecord\dbMySQLConnector;
23
use samsonphp\generator\Generator;
24
25
/**
26
 * SamsonCMS API
27
 * @package samsoncms\api
28
 */
29
class CMS extends CompressableService
0 ignored issues
show
Deprecated Code introduced by
The class samson\core\CompressableService has been deprecated with message: Just implement samsonframework\core\CompressInterface

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

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

Loading history...
30
{
31
    /** Database entity name for relations between material and navigation */
32
    const MATERIAL_NAVIGATION_RELATION_ENTITY = '\samson\activerecord\structurematerial';
33
    /** Database entity name for relations between material and images */
34
    const MATERIAL_IMAGES_RELATION_ENTITY = GalleryField::class;
35
    /** Database entity name for relations between additional fields and navigation */
36
    const FIELD_NAVIGATION_RELATION_ENTITY = '\samson\activerecord\structurefield';
37
    /** Database entity name for relations between material and additional fields values */
38
    const MATERIAL_FIELD_RELATION_ENTITY = MaterialField::class;
39
40
    /** Identifier */
41
    protected $id = 'cmsapi2';
42
43
    /** @var \samsonframework\orm\DatabaseInterface */
44
    protected $database;
45
46
    /** @var array[string] Collection of generated queries */
47
    protected $queries;
48
49
    /** @var string Database table names prefix */
50
    public $tablePrefix = '';
51
52
    /**
53
     * CMS constructor.
54
     *
55
     * @param string $path
56
     * @param ResourcesInterface $resources
57
     * @param SystemInterface $system
58
     */
59
    public function  __construct($path, ResourcesInterface $resources, SystemInterface $system)
0 ignored issues
show
Coding Style introduced by
Expected "function abc(...)"; found "function abc(...)"
Loading history...
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 2 found
Loading history...
60
    {
61
        // TODO: This should changed to normal DI
62
        $this->database = db();
63
64
        parent::__construct($path, $resources, $system);
65
    }
66
67
    /**
68
     * Module initialization.
69
     *
70
     * @param array $params Initialization parameters
71
     * @return boolean|null Initialization result
72
     */
73
    public function init(array $params = array())
74
    {
75
        $this->rewriteEntityLocale();
76
    }
77
78
    public function beforeCompress(& $obj = null, array & $code = null)
79
    {
80
81
    }
82
83
    public function afterCompress(& $obj = null, array & $code = null)
84
    {
85
        // Iterate through generated php code
86
        $files = array();
87
        foreach (\samson\core\File::dir($this->cache_path, 'php', '', $files, 1) as $file) {
88
            // No namespace for global function file
89
            $ns = strpos($file, 'func') === false ? __NAMESPACE__ : '';
90
91
            // Compress generated php code
92
            $obj->compress_php($file, $this, $code, $ns);
93
        }
94
    }
95
96
    /**
97
     * Entity additional fields localization support.
98
     */
99
    protected function rewriteEntityLocale()
100
    {
101
        // Iterate all generated entity classes
102
        foreach (get_declared_classes() as $entityClass) {
103
            if (is_subclass_of($entityClass, '\samsoncms\api\Entity')) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of returns inconsistent results on some PHP versions for interfaces; you could instead use ReflectionClass::implementsInterface.
Loading history...
104
                // Insert current application locale
105
                str_replace('@locale', locale(), $entityClass::$_sql_select);
106
            }
107
        }
108
    }
109
110
    //[PHPCOMPRESSOR(remove,start)]
111
    /**
112
     * Read SQL file with variables placeholders pasting
113
     * @param string $filePath SQL file for reading
114
     * @param string $prefix Prefix for addition
115
     * @return array Collection of SQL command texts
116
     */
117
    public function readSQL($filePath, $prefix = '')
118
    {
119
        $sql = '';
120
121
        // Build path to SQL folder
122
        if (file_exists($filePath)) {
123
            // Replace prefix
124
            $sql = str_replace('@prefix', $prefix, file_get_contents($filePath));
125
        }
126
127
        // Split queries
128
        $sqlCommands = explode(';', str_replace("\n", '', $sql));
129
130
        // Always return array
131
        return array_filter(is_array($sqlCommands) ? $sqlCommands : array($sqlCommands));
132
    }
133
134
    /**
135
     * @see ModuleConnector::prepare()
136
     */
137
    public function prepare()
138
    {
139
        // Create cms_version
140
        $this->database->execute('
141
CREATE TABLE IF NOT EXISTS `cms_version`  (
142
  `version` varchar(15) NOT NULL DEFAULT \'30\'
143
) ENGINE=InnoDB DEFAULT CHARSET=utf8;'
144
        );
145
146
        // Perform this migration and execute only once
147
        if ($this->migrator() != 40) {
148
            // Perform SQL table creation
149
            $path = __DIR__ . '/../sql/';
150
            foreach (array_slice(scandir($path), 2) as $file) {
151
                trace('Performing database script ['.$file.']');
152
                foreach ($this->readSQL($path . $file, $this->tablePrefix) as $sql) {
153
                    $this->database->execute($sql);
154
                }
155
            }
156
            $this->migrator(40);
157
        }
158
159
        // Initiate migration mechanism
160
        $this->database->migration(get_class($this), array($this, 'migrator'));
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface samsonframework\orm\DatabaseInterface as the method migration() does only exist in the following implementations of said interface: samson\activerecord\dbMySQL.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
161
162
                // Define permanent table relations
163
        new TableRelation('material', 'user', 'UserID', 0, 'user_id');
164
        new TableRelation('material', 'gallery', 'MaterialID', TableRelation::T_ONE_TO_MANY);
165
        new TableRelation('material', 'materialfield', 'MaterialID', TableRelation::T_ONE_TO_MANY);
166
        new TableRelation('material', 'field', 'materialfield.FieldID', TableRelation::T_ONE_TO_MANY);
167
        new TableRelation('material', 'structurematerial', 'MaterialID', TableRelation::T_ONE_TO_MANY);
168
        new TableRelation('material', 'structure', 'structurematerial.StructureID', TableRelation::T_ONE_TO_MANY);
169
        new TableRelation('materialfield', 'field', 'FieldID');
170
        new TableRelation('materialfield', 'material', 'MaterialID');
171
        new TableRelation('structurematerial', 'structure', 'StructureID');
172
        new TableRelation('structurematerial', 'materialfield', 'MaterialID', TableRelation::T_ONE_TO_MANY);
173
        new TableRelation('structurematerial', 'material', 'MaterialID', TableRelation::T_ONE_TO_MANY);
174
        new TableRelation('structure', 'material', 'structurematerial.MaterialID', TableRelation::T_ONE_TO_MANY, null, 'manymaterials');
175
        new TableRelation('structure', 'gallery', 'structurematerial.MaterialID', TableRelation::T_ONE_TO_MANY, null, 'manymaterials');
176
        /*new TableRelation( 'structure', 'material', 'MaterialID' );*/
177
        new TableRelation('structure', 'user', 'UserID', 0, 'user_id');
178
        new TableRelation('structure', 'materialfield', 'material.MaterialID', TableRelation::T_ONE_TO_MANY, 'MaterialID', '_mf');
179
        new TableRelation('structure', 'structurematerial', 'StructureID', TableRelation::T_ONE_TO_MANY);
180
        //new TableRelation('related_materials', 'material', 'first_material', TableRelation::T_ONE_TO_MANY, 'MaterialID');
181
        //new TableRelation('related_materials', 'materialfield', 'first_material', TableRelation::T_ONE_TO_MANY, 'MaterialID');
182
        new TableRelation('field', 'structurefield', 'FieldID');
183
        new TableRelation('field', 'structure', 'structurefield.StructureID');
184
        new TableRelation('structurefield', 'field', 'FieldID');
185
        new TableRelation('structurefield', 'materialfield', 'FieldID');
186
        new TableRelation('structurefield', 'material', 'materialfield.MaterialID');
187
        new TableRelation('structure', 'structure_relation', 'StructureID', TableRelation::T_ONE_TO_MANY, 'parent_id', 'children_relations');
188
        new TableRelation('structure', 'structure', 'children_relations.child_id', TableRelation::T_ONE_TO_MANY, 'StructureID', 'children');
189
        new TableRelation('structure', 'structure_relation', 'StructureID', TableRelation::T_ONE_TO_MANY, 'child_id', 'parents_relations');
190
        new TableRelation('structure', 'structure', 'parents_relations.parent_id', TableRelation::T_ONE_TO_MANY, 'StructureID', 'parents');
191
        new TableRelation('structurematerial', 'structure_relation', 'StructureID', TableRelation::T_ONE_TO_MANY, 'parent_id');
192
        new TableRelation('groupright', 'right', 'RightID', TableRelation::T_ONE_TO_MANY);
193
194
        // TODO: Should be removed
195
        m('activerecord')->relations();
0 ignored issues
show
Deprecated Code introduced by
The function m() has been deprecated with message: Use $this->system->module() in module context

This function has been deprecated. The supplier of the file has supplied an explanatory message.

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

Loading history...
196
197
        // Create database analyzer
198
        $generator = new Analyzer($this->database);
199
        // Analyze database structure and get entities metadata
200
        foreach ($generator->analyze(Metadata::TYPE_DEFAULT) as $metadata) {
201
            // Create entity generated class names
202
            $entityFile = $this->cache_path.$metadata->entity.'.php';
203
            $queryFile = $this->cache_path.$metadata->entity.'Query.php';
204
            $collectionFile = $this->cache_path.$metadata->entity.'Collection.php';
205
206
            // Create entity query class generator
207
            $entityGenerator = new Entity(new Generator(__NAMESPACE__.'\\generated'), $metadata);
208
            // Create entity query class generator
209
            $queryGenerator = new Query(new Generator(__NAMESPACE__.'\\generated'), $metadata);
210
            // Create entity query collection class generator
211
            $collectionGenerator = new Collection(new Generator(__NAMESPACE__.'\\generated'), $metadata);
212
213
            // Create entity query class files
214
            file_put_contents($entityFile, '<?php' . $entityGenerator->generate());
215
            file_put_contents($queryFile, '<?php' . $queryGenerator->generate());
216
            file_put_contents($collectionFile, '<?php' . $collectionGenerator->generate());
217
218
            // Require files
219
            require($entityFile);
220
            require($queryFile);
221
            require($collectionFile);
222
223
            // Iterate entity additional fields
224 View Code Duplication
            foreach ($metadata->allFieldCmsTypes as $fieldID => $fieldType) {
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...
225
                // We need only gallery fields
226
                if ($fieldType === Field::TYPE_GALLERY) {
0 ignored issues
show
Coding Style introduced by
Blank line found at start of control structure
Loading history...
227
228
                    $fieldName = $metadata->allFieldIDs[$fieldID];
229
                    // Declare class
230
                    $this->generateQuerableClassHeader(
0 ignored issues
show
Documentation Bug introduced by
The method generateQuerableClassHeader does not exist on object<samsoncms\api\CMS>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
231
                        $metadata,
232
                        ucfirst($fieldName) . 'Gallery',
233
                        '\\' . \samsoncms\api\Gallery::class,
234
                        array(\samsoncms\api\Renderable::class)
235
                    );
236
237
                    return $this->generator
0 ignored issues
show
Documentation introduced by
The property generator does not exist on object<samsoncms\api\CMS>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
238
                        ->text($this->generateConstructorGalleryClass(
0 ignored issues
show
Documentation Bug introduced by
The method generateConstructorGalleryClass does not exist on object<samsoncms\api\CMS>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
239
                            $metadata->entity . '::F_' . strtoupper($fieldName) . '_ID',
240
                            $metadata->entity
241
                        ))
242
                        ->endClass()
243
                        ->flush();
244
                }
245
            }
246
        }
247
248
//        // Generate entities classes file
249
//        $generatorApi = new GeneratorApi($this->database);
250
//        //$queryGenerator = new Query($this->database);
251
//
252
//        // Create cache file
253
//        $file = md5($generatorApi->entityHash()).'.php';
254
//        if ($this->cache_refresh($file)) {
255
//            file_put_contents($file, '<?php ' . $generatorApi->createEntityClasses());
256
//        }
257
//
258
//        // Include entities file
259
//        require($file);
260
261
        return parent::prepare();
262
    }
263
264
    /**
265
     * Handler for CMSAPI database version manipulating
266
     * @param string $toVersion Version to switch to
267
     * @return string Current database version
268
     */
269
    public function migrator($toVersion = null)
270
    {
271
        // If something passed - change database version to it
272
        if (func_num_args()) {
273
            // Save current version to special db table
274
            $this->database->execute(
275
                "ALTER TABLE  `" . dbMySQLConnector::$prefix . "cms_version`
276
                CHANGE  `version`  `version` VARCHAR( 15 ) CHARACTER SET utf8
277
                COLLATE utf8_general_ci NOT NULL DEFAULT  '" . $toVersion . "';"
278
            );
279
            die('Database successfully migrated to [' . $toVersion . ']');
280
        } else { // Return current database version
281
            $version_row = $this->database->fetch('SHOW COLUMNS FROM `' . dbMySQLConnector::$prefix . 'cms_version`');
282
            if (isset($version_row[0]['Default'])) {
283
                return $version_row[0]['Default'];
284
            } else {
285
                return 0;
286
            }
287
        }
288
    }
289
    //[PHPCOMPRESSOR(remove,end)]
290
}
291