Completed
Pull Request — master (#1)
by Rougin
02:23
created

ObjectTrait::getModel()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5.1502

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 9
cts 11
cp 0.8182
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 10
nc 4
nop 2
crap 5.1502
1
<?php
2
3
namespace Rougin\Wildfire\Traits;
4
5
use Rougin\Describe\Column;
6
7
use Rougin\Wildfire\CodeigniterModel;
8
9
/**
10
 * Object Trait
11
 *
12
 * @package Wildfire
13
 * @author  Rougin Royce Gutib <[email protected]>
14
 *
15
 * @property \Rougin\Describe\Describe $describe
16
 * @property string                    $table
17
 */
18
trait ObjectTrait
19
{
20
    /**
21
     * @var array
22
     */
23
    protected $tables = [];
24
25
    /**
26
     * Creates an object from the specified table and row.
27
     *
28
     * @param  string  $table
29
     * @param  object  $row
30
     * @param  boolean $isForeignKey
31
     * @return array
32
     */
33 27
    protected function createObject($table, $row, $isForeignKey = false)
34
    {
35 27
        list($model, $newTable) = $this->getModel($table, $isForeignKey);
36
37 27
        if (! array_key_exists($newTable, $this->tables)) {
38 21
            $tableInfo = $this->describe->getTable($newTable);
39
40 21
            $this->tables[$newTable] = $tableInfo;
41 21
        } else {
42 24
            $tableInfo = $this->tables[$newTable];
43
        }
44
45 27
        $properties = $this->getModelProperties($model);
46
47 27
        foreach ($tableInfo as $column) {
48 27
            $key = $column->getField();
49
50 27
            $inHiddenColumns = ! empty($properties['hidden']) && in_array($key, $properties['hidden']);
51 27
            $inColumns       = ! empty($properties['columns']) && ! in_array($key, $properties['columns']);
52
53 27
            if ($inColumns || $inHiddenColumns) {
54 27
                continue;
55
            }
56
57 27
            $model->$key = $row->$key;
58
59 27
            $this->setForeignField($model, $column);
60 27
        }
61
62 27
        return $model;
63
    }
64
65
    /**
66
     * Finds the row from the specified ID or with the list of delimiters from
67
     * the specified table.
68
     *
69
     * @param  string         $table
70
     * @param  array|integer  $delimiters
71
     * @param  boolean        $isForeignKey
72
     * @return object|boolean
73
     */
74
    abstract protected function find($table, $delimiters = [], $isForeignKey = false);
75
76
    /**
77
     * Returns the values from the model's properties.
78
     *
79
     * @param  \CI_Model|\Rougin\Wildfire\CodeigniterModel $model
80
     * @return array
81
     */
82 27
    protected function getModelProperties($model)
83
    {
84 27
        $properties = [ 'column' => [], 'hidden' => [] ];
85
86 27
        if (method_exists($model, 'getColumns')) {
87 12
            $properties['columns'] = $model->getColumns();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class CI_Model as the method getColumns() does only exist in the following sub-classes of CI_Model: Rougin\Wildfire\CodeigniterModel. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
88 27
        } elseif (property_exists($model, 'columns')) {
89
            // NOTE: To be removed in v1.0.0
90 15
            $properties['columns'] = $model->columns;
0 ignored issues
show
Bug introduced by
The property columns does not seem to exist in CI_Model.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
91 15
        }
92
93
        // NOTE: To be removed in v1.0.0 (if condition only)
94 27
        if (method_exists($model, 'getHiddenColumns')) {
95 12
            $properties['hidden'] = $model->getHiddenColumns();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class CI_Model as the method getHiddenColumns() does only exist in the following sub-classes of CI_Model: Rougin\Wildfire\CodeigniterModel. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
96 12
        }
97
98 27
        return $properties;
99
    }
100
101
    /**
102
     * Sets the foreign field of the column, if any.
103
     *
104
     * @param  \CI_Model               $model
105
     * @param  \Rougin\Describe\Column $column
106
     * @return void
107
     */
108 27
    protected function setForeignField(\CI_Model $model, Column $column)
109
    {
110 27
        if (! $column->isForeignKey()) {
111 27
            return;
112
        }
113
114
        $columnName    = $column->getField();
115
        $foreignColumn = $column->getReferencedField();
116
        $foreignTable  = $column->getReferencedTable();
117
118
        $delimiters  = [ $foreignColumn => $model->$columnName ];
119
        $foreignData = $this->find($foreignTable, $delimiters, true);
120
        $newColumn   = $this->getTableName($foreignTable, true);
121
122
        $model->$newColumn = $foreignData;
123
    }
124
125
    /**
126
     * Gets the model class of the said table.
127
     *
128
     * @param  string|null $table
129
     * @param  boolean     $isForeignKey
130
     * @return array
131
     */
132 36
    protected function getModel($table = null, $isForeignKey = false)
133
    {
134 36
        if (empty($table) && empty($this->table)) {
135 6
            return [ null, '' ];
136
        }
137
138 36
        $newTable = $this->getTableName($table, $isForeignKey);
139 36
        $newModel = new $newTable;
140
141 36
        if (method_exists($newModel, 'getTableName')) {
142 15
            $newTable = $newModel->getTableName();
143 36
        } elseif (property_exists($newModel, 'table')) {
144
            // NOTE: To be removed in v1.0.0
145
            $newTable = $newModel->table;
146
        }
147
148 36
        return [ $newModel, strtolower($newTable) ];
149
    }
150
151
    /**
152
     * Parses the table name from Describe class.
153
     *
154
     * @param  string  $table
155
     * @param  boolean $isForeignKey
156
     * @return string
157
     */
158
    abstract protected function getTableName($table, $isForeignKey = false);
159
}
160