1
2/**
3 * This file is part of the Phalcon Framework.
4 *
5 * (c) Phalcon Team <team@phalcon.io>
6 *
7 * For the full copyright and license information, please view the LICENSE.txt
8 * file that was distributed with this source code.
9 */
10
11namespace Phalcon\Mvc\Model\MetaData\Strategy;
12
13use Phalcon\Di\DiInterface;
14use Phalcon\Db\Adapter\AdapterInterface;
15use Phalcon\Db\Column;
16use Phalcon\Mvc\ModelInterface;
17use Phalcon\Mvc\Model\Exception;
18use Phalcon\Mvc\Model\MetaData;
19
20/**
21 * Phalcon\Mvc\Model\MetaData\Strategy\Introspection
22 *
23 * Queries the table meta-data in order to introspect the model's metadata
24 */
25class Introspection implements StrategyInterface
26{
27    /**
28     * Read the model's column map, this can't be inferred
29     */
30    final public function getColumnMaps(<ModelInterface> model, <DiInterface> container) -> array
31    {
32        var orderedColumnMap, userColumnMap, reversedColumnMap, name, userName;
33
34        let orderedColumnMap = null;
35        let reversedColumnMap = null;
36
37        /**
38         * Check for a columnMap() method on the model
39         */
40        if method_exists(model, "columnMap") {
41            let userColumnMap = model->{"columnMap"}();
42
43            if unlikely typeof userColumnMap != "array" {
44                throw new Exception("columnMap() not returned an array");
45            }
46
47            let reversedColumnMap = [],
48                orderedColumnMap = userColumnMap;
49
50            for name, userName in userColumnMap {
51                let reversedColumnMap[userName] = name;
52            }
53        }
54
55        /**
56         * Store the column map
57         */
58        return [orderedColumnMap, reversedColumnMap];
59    }
60
61    /**
62     * The meta-data is obtained by reading the column descriptions from the database information schema
63     */
64    final public function getMetaData(<ModelInterface> model, <DiInterface> container) -> array
65    {
66        var schema, table, readConnection, columns, attributes, primaryKeys,
67            nonPrimaryKeys, numericTyped, notNull, fieldTypes, automaticDefault,
68            identityField, fieldBindTypes, defaultValues, column, fieldName,
69            defaultValue, emptyStringValues;
70        string completeTable;
71
72        let schema = model->getSchema(),
73            table  = model->getSource();
74
75        /**
76         * Check if the mapped table exists on the database
77         */
78        let readConnection = model->getReadConnection();
79
80        if unlikely !readConnection->tableExists(table, schema) {
81            if schema {
82                let completeTable = schema . "'.'" . table;
83            } else {
84                let completeTable = table;
85            }
86
87            /**
88             * The table not exists
89             */
90            throw new Exception(
91                "Table '" . completeTable . "' doesn't exist in database when dumping meta-data for " . get_class(model)
92            );
93        }
94
95        /**
96         * Try to describe the table
97         */
98        let columns = readConnection->describeColumns(table, schema);
99
100        if unlikely !count(columns) {
101            if schema {
102                let completeTable = schema . "'.'" . table;
103            } else {
104                let completeTable = table;
105            }
106
107            /**
108             * The table not exists
109             */
110            throw new Exception(
111                "Cannot obtain table columns for the mapped source '" . completeTable . "' used in model " . get_class(model)
112            );
113        }
114
115        /**
116         * Initialize meta-data
117         */
118        let attributes = [];
119        let primaryKeys = [];
120        let nonPrimaryKeys = [];
121        let numericTyped = [];
122        let notNull = [];
123        let fieldTypes = [];
124        let fieldBindTypes = [];
125        let automaticDefault = [];
126        let identityField = false;
127        let defaultValues = [];
128        let emptyStringValues = [];
129
130        for column in columns {
131            let fieldName = column->getName(),
132                attributes[] = fieldName;
133
134            /**
135             * To mark fields as primary keys
136             */
137            if column->isPrimary() {
138                let primaryKeys[] = fieldName;
139            } else {
140                let nonPrimaryKeys[] = fieldName;
141            }
142
143            /**
144             * To mark fields as numeric
145             */
146            if column->isNumeric() {
147                let numericTyped[fieldName] = true;
148            }
149
150            /**
151             * To mark fields as not null
152             */
153            if column->isNotNull() {
154                let notNull[] = fieldName;
155            }
156
157            /**
158             * To mark fields as identity columns
159             */
160            if column->isAutoIncrement() {
161                let identityField = fieldName;
162            }
163
164            /**
165             * To get the internal types
166             */
167            let fieldTypes[fieldName] = column->getType();
168
169            /**
170             * To mark how the fields must be escaped
171             */
172            let fieldBindTypes[fieldName] = column->getBindType();
173
174            /**
175             * If column has default value or column is nullable and default value is null
176             */
177            let defaultValue = column->getDefault();
178
179            if defaultValue !== null || !column->isNotNull() {
180                if !column->isAutoIncrement() {
181                    let defaultValues[fieldName] = defaultValue;
182                }
183            }
184        }
185
186        /**
187         * Create an array using the MODELS_* constants as indexes
188         */
189        return [
190            MetaData::MODELS_ATTRIBUTES               : attributes,
191            MetaData::MODELS_PRIMARY_KEY              : primaryKeys,
192            MetaData::MODELS_NON_PRIMARY_KEY          : nonPrimaryKeys,
193            MetaData::MODELS_NOT_NULL                 : notNull,
194            MetaData::MODELS_DATA_TYPES               : fieldTypes,
195            MetaData::MODELS_DATA_TYPES_NUMERIC       : numericTyped,
196            MetaData::MODELS_IDENTITY_COLUMN          : identityField,
197            MetaData::MODELS_DATA_TYPES_BIND          : fieldBindTypes,
198            MetaData::MODELS_AUTOMATIC_DEFAULT_INSERT : automaticDefault,
199            MetaData::MODELS_AUTOMATIC_DEFAULT_UPDATE : automaticDefault,
200            MetaData::MODELS_DEFAULT_VALUES           : defaultValues,
201            MetaData::MODELS_EMPTY_STRING_VALUES      : emptyStringValues
202        ];
203    }
204}
205