Passed
Push — master ( 34dd63...33989f )
by Andreas
22:47
created

is_midcom_db_object()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 8.125

Importance

Changes 0
Metric Value
cc 5
eloc 5
nc 4
nop 1
dl 0
loc 10
ccs 3
cts 6
cp 0.5
crap 8.125
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package midcom.services
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
use midgard\portable\api\mgdobject;
10
11
/**
12
 * <b>How to write database class definitions:</b>
13
 *
14
 * The general idea is to provide MidCOM with a way to hook into every database interaction
15
 * between the component and the Midgard core.
16
 *
17
 * Since PHP does not allow for multiple inheritance (which would be really useful here),
18
 * a decorator pattern is used, which connects the class you actually use in your component
19
 * and the original MgdSchema class while at the same time routing all function calls through
20
 * midcom_core_dbaobject.
21
 *
22
 * The class loader does not require much information when registering classes:
23
 * An example declaration looks like this:
24
 *
25
 * <code>
26
 * [
27
 *     'midgard_article' => 'midcom_db_article'
28
 * ]
29
 * </code>
30
 *
31
 * The key is the MgdSchema class name from that you want to use. The class specified must exist.
32
 *
33
 * The value is the name of the MidCOM base class you intend to create.
34
 * It is checked for basic validity against the PHP restrictions on symbol naming, but the
35
 * class itself is not checked for existence. You <i>must</i> declare the class as listed at
36
 * all times, as typecasting and detection is done using this metadata property in the core.
37
 *
38
 * <b>Inherited class requirements</b>
39
 *
40
 * The classes you inherit from the intermediate stub classes must at this time satisfy one
41
 * requirement: You have to declare the midcom and mgdschema classnames:
42
 *
43
 * <code>
44
 * class midcom_db_article
45
 *     extends midcom_core_dbaobject
46
 * {
47
 *      public $__midcom_class_name__ = __CLASS__;
48
 *      public $__mgdschema_class_name__ = 'midgard_article';
49
 *
50
 * </code>
51
 *
52
 * @package midcom.services
53
 */
54
class midcom_services_dbclassloader
55
{
56
    /**
57
     * Simple helper to check whether we are dealing with a MgdSchema or MidCOM DBA
58
     * object or a subclass thereof.
59
     *
60
     * @param string|object $object The object to check
61
     */
62 4
    public function is_mgdschema_object($object) : bool
63
    {
64
        // Sometimes we might get class string instead of an object
65 4
        if (is_string($object)) {
66 2
            $object = new $object;
67
        }
68 4
        if ($this->is_midcom_db_object($object)) {
69
            return true;
70
        }
71
72 4
        return is_a($object, mgdobject::class);
73
    }
74
75
    /**
76
     * Get component name associated with a class name to get its DBA classes defined
77
     */
78 33
    public function get_component_for_class(string $classname) : ?string
79
    {
80 33
        $class_parts = array_filter(explode('_', $classname));
81
        // Fix for incorrectly named classes
82
        $component_map = [
83 33
            'midgard' => 'midcom',
84
            'org.openpsa.campaign' => 'org.openpsa.directmarketing',
85
            'org.openpsa.link' => 'org.openpsa.directmarketing',
86
            'org.openpsa.document' => 'org.openpsa.documents',
87
            'org.openpsa.organization' => 'org.openpsa.contacts',
88
            'org.openpsa.person' => 'org.openpsa.contacts',
89
            'org.openpsa.role' => 'org.openpsa.contacts',
90
            'org.openpsa.member' => 'org.openpsa.contacts',
91
            'org.openpsa.salesproject' => 'org.openpsa.sales',
92
            'org.openpsa.offer' => 'org.openpsa.sales',
93
            'org.openpsa.event' => 'org.openpsa.calendar',
94
            'org.openpsa.eventmember' => 'org.openpsa.calendar',
95
            'org.openpsa.invoice' => 'org.openpsa.invoices',
96
            'org.openpsa.billing' => 'org.openpsa.invoices',
97
            'org.openpsa.query' => 'org.openpsa.reports',
98
            'org.openpsa.task' => 'org.openpsa.projects',
99
            'org.openpsa.project' => 'org.openpsa.projects',
100
            'org.openpsa.hour' => 'org.openpsa.expenses'
101
        ];
102
103 33
        while (!empty($class_parts)) {
104 33
            $component = implode('.', $class_parts);
105 33
            if (array_key_exists($component, $component_map)) {
106 22
                $component = $component_map[$component];
107
            }
108
109 33
            if (midcom::get()->componentloader->is_installed($component)) {
110 32
                return $component;
111
            }
112 31
            array_pop($class_parts);
113
        }
114
115 1
        return null;
116
    }
117
118
    /**
119
     * Get a MidCOM DB class name for a MgdSchema Object.
120
     * We also ensure that the corresponding component has been loaded.
121
     *
122
     * @param string|object $object The object (or classname) to check
123
     * @return string The corresponding MidCOM DB class name, false otherwise.
124
     */
125 296
    public function get_midcom_class_name_for_mgdschema_object($object)
126
    {
127 296
        static $dba_classes_by_mgdschema = [];
128
129 296
        if (is_string($object)) {
130
            // In some cases we get a class name instead
131 232
            $classname = $object;
132 157
        } elseif (is_object($object)) {
133 157
            $classname = get_class($object);
134
        } else {
135
            debug_print_r("Invalid input provided", $object, MIDCOM_LOG_WARN);
136
            return false;
137
        }
138
139 296
        if (isset($dba_classes_by_mgdschema[$classname])) {
140 294
            return $dba_classes_by_mgdschema[$classname];
141
        }
142
143 4
        if (!$this->is_mgdschema_object($object)) {
144
            debug_add("{$classname} is not an MgdSchema object, not resolving to MidCOM DBA class", MIDCOM_LOG_WARN);
145
            $dba_classes_by_mgdschema[$classname] = false;
146
            return false;
147
        }
148
149 4
        if ($classname == midcom::get()->config->get('person_class')) {
150
            $component = 'midcom';
151
        } else {
152 4
            $component = $this->get_component_for_class($classname);
153 4
            if (!$component) {
154
                debug_add("Component for class {$classname} cannot be found", MIDCOM_LOG_WARN);
155
                $dba_classes_by_mgdschema[$classname] = false;
156
                return false;
157
            }
158
        }
159 4
        $definitions = $this->get_component_classes($component);
160
161
        //TODO: This allows components to override midcom classes fx. Do we want that?
162 4
        $dba_classes_by_mgdschema = array_merge($dba_classes_by_mgdschema, $definitions);
163
164 4
        if (array_key_exists($classname, $dba_classes_by_mgdschema)) {
165 4
            return $dba_classes_by_mgdschema[$classname];
166
        }
167
168
        debug_add("{$classname} cannot be resolved to any DBA class name");
169
        $dba_classes_by_mgdschema[$classname] = false;
170
        return false;
171
    }
172
173
    /**
174
     * Get an MgdSchema class name for a MidCOM DBA class name
175
     *
176
     * @return string The corresponding MidCOM DBA class name, false otherwise.
177
     */
178 9
    public function get_mgdschema_class_name_for_midcom_class(string $classname)
179
    {
180 9
        static $mapping = [];
181
182 9
        if (!array_key_exists($classname, $mapping)) {
183 6
            $mapping[$classname] = false;
184
185 6
            if (class_exists($classname)) {
186 6
                $dummy_object = new $classname();
187 6
                if ($this->is_midcom_db_object($dummy_object)) {
188 6
                    $mapping[$classname] = $dummy_object->__mgdschema_class_name__;
189
                }
190
            }
191
        }
192
193 9
        return $mapping[$classname];
194
    }
195
196
    /**
197
     * Simple helper to check whether we are dealing with a MidCOM Database object
198
     * or a subclass thereof.
199
     *
200
     * @param object|string $object The object (or classname) to check
201
     */
202 501
    public function is_midcom_db_object($object) : bool
203
    {
204 501
        if (is_object($object)) {
205 501
            return ($object instanceof midcom_core_dbaobject || $object instanceof midcom_core_dbaproxy);
206
        }
207
        if (is_string($object) && class_exists($object)) {
208
            return $this->is_midcom_db_object(new $object);
209
        }
210
211
        return false;
212
    }
213
214 10
    public function get_component_classes(string $component) : array
215
    {
216 10
        $map = midcom::get()->componentloader->get_manifest($component)->class_mapping;
217 10
        if ($component == 'midcom') {
218
            $map[midcom::get()->config->get('person_class')] = midcom_db_person::class;
219
        }
220 10
        return $map;
221
    }
222
}
223