Passed
Push — master ( bee4ae...fc5262 )
by Andreas
16:28
created

midcom_services_dbclassloader   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 171
Duplicated Lines 0 %

Test Coverage

Coverage 75.76%

Importance

Changes 0
Metric Value
eloc 81
dl 0
loc 171
ccs 50
cts 66
cp 0.7576
rs 10
c 0
b 0
f 0
wmc 26

6 Methods

Rating   Name   Duplication   Size   Complexity  
A is_midcom_db_object() 0 10 5
A get_mgdschema_class_name_for_midcom_class() 0 17 4
A get_component_for_class() 0 38 4
A is_mgdschema_object() 0 11 3
B get_midcom_class_name_for_mgdschema_object() 0 46 8
A get_component_classes() 0 7 2
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
 * Array
27
 * (
28
 *     'midgard_article' => 'midcom_db_article'
29
 * )
30
 * </code>
31
 *
32
 * The key is the MgdSchema class name from that you want to use. The class specified must exist.
33
 *
34
 * The value is the name of the MidCOM base class you intend to create.
35
 * It is checked for basic validity against the PHP restrictions on symbol naming, but the
36
 * class itself is not checked for existence. You <i>must</i> declare the class as listed at
37
 * all times, as typecasting and detection is done using this metadata property in the core.
38
 *
39
 * <b>Inherited class requirements</b>
40
 *
41
 * The classes you inherit from the intermediate stub classes must at this time satisfy one
42
 * requirement: You have to declare the midcom and mgdschema classnames:
43
 *
44
 * <code>
45
 * class midcom_db_article
46
 *     extends midcom_core_dbaobject
47
 * {
48
 *      public $__midcom_class_name__ = __CLASS__;
49
 *      public $__mgdschema_class_name__ = 'midgard_article';
50
 *
51
 * </code>
52
 *
53
 * @package midcom.services
54
 */
55
class midcom_services_dbclassloader
56
{
57
    /**
58
     * Simple helper to check whether we are dealing with a MgdSchema or MidCOM DBA
59
     * object or a subclass thereof.
60
     *
61
     * @param object $object The object to check
62
     */
63 280
    public function is_mgdschema_object($object) : bool
64
    {
65
        // Sometimes we might get class string instead of an object
66 280
        if (is_string($object)) {
0 ignored issues
show
introduced by
The condition is_string($object) is always false.
Loading history...
67 3
            $object = new $object;
68
        }
69 280
        if ($this->is_midcom_db_object($object)) {
70
            return true;
71
        }
72
73 280
        return is_a($object, mgdobject::class);
74
    }
75
76
    /**
77
     * Get component name associated with a class name to get its DBA classes defined
78
     *
79
     * @param string $classname Class name to load a component for
80
     * @return string component name if found for the class, false otherwise
81
     */
82 32
    public function get_component_for_class(string $classname)
83
    {
84 32
        $class_parts = array_filter(explode('_', $classname));
85
        // Fix for incorrectly named classes
86
        $component_map = [
87 32
            'midgard' => 'midcom',
88
            'org.openpsa.campaign' => 'org.openpsa.directmarketing',
89
            'org.openpsa.link' => 'org.openpsa.directmarketing',
90
            'org.openpsa.document' => 'org.openpsa.documents',
91
            'org.openpsa.organization' => 'org.openpsa.contacts',
92
            'org.openpsa.person' => 'org.openpsa.contacts',
93
            'org.openpsa.role' => 'org.openpsa.contacts',
94
            'org.openpsa.member' => 'org.openpsa.contacts',
95
            'org.openpsa.salesproject' => 'org.openpsa.sales',
96
            'org.openpsa.offer' => 'org.openpsa.sales',
97
            'org.openpsa.event' => 'org.openpsa.calendar',
98
            'org.openpsa.eventmember' => 'org.openpsa.calendar',
99
            'org.openpsa.invoice' => 'org.openpsa.invoices',
100
            'org.openpsa.billing' => 'org.openpsa.invoices',
101
            'org.openpsa.query' => 'org.openpsa.reports',
102
            'org.openpsa.task' => 'org.openpsa.projects',
103
            'org.openpsa.project' => 'org.openpsa.projects',
104
            'org.openpsa.hour' => 'org.openpsa.projects'
105
        ];
106
107 32
        while (!empty($class_parts)) {
108 32
            $component = implode('.', $class_parts);
109 32
            if (array_key_exists($component, $component_map)) {
110 20
                $component = $component_map[$component];
111
            }
112
113 32
            if (midcom::get()->componentloader->is_installed($component)) {
114 32
                return $component;
115
            }
116 31
            array_pop($class_parts);
117
        }
118
119
        return false;
120
    }
121
122
    /**
123
     * Get a MidCOM DB class name for a MgdSchema Object.
124
     *
125
     * @param string|object $object The object (or classname) to check
126
     * @return string The corresponding MidCOM DB class name, false otherwise.
127
     */
128 290
    public function get_midcom_class_name_for_mgdschema_object($object)
129
    {
130 290
        static $dba_classes_by_mgdschema = [];
131
132 290
        if (is_string($object)) {
133
            // In some cases we get a class name instead
134 217
            $classname = $object;
135 172
        } elseif (is_object($object)) {
136 172
            $classname = get_class($object);
137
        } else {
138
            debug_print_r("Invalid input provided", $object, MIDCOM_LOG_WARN);
139
            return false;
140
        }
141
142 290
        if (isset($dba_classes_by_mgdschema[$classname])) {
143 289
            return $dba_classes_by_mgdschema[$classname];
144
        }
145
146 5
        if (!$this->is_mgdschema_object($object)) {
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type string; however, parameter $object of midcom_services_dbclassl...::is_mgdschema_object() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

146
        if (!$this->is_mgdschema_object(/** @scrutinizer ignore-type */ $object)) {
Loading history...
147
            debug_add("{$classname} is not an MgdSchema object, not resolving to MidCOM DBA class", MIDCOM_LOG_WARN);
148
            $dba_classes_by_mgdschema[$classname] = false;
149
            return false;
150
        }
151
152 5
        if ($classname == midcom::get()->config->get('person_class')) {
153
            $component = 'midcom';
154
        } else {
155 5
            $component = $this->get_component_for_class($classname);
156 5
            if (!$component) {
157
                debug_add("Component for class {$classname} cannot be found", MIDCOM_LOG_WARN);
158
                $dba_classes_by_mgdschema[$classname] = false;
159
                return false;
160
            }
161
        }
162 5
        $definitions = $this->get_component_classes($component);
163
164
        //TODO: This allows components to override midcom classes fx. Do we want that?
165 5
        $dba_classes_by_mgdschema = array_merge($dba_classes_by_mgdschema, $definitions);
166
167 5
        if (array_key_exists($classname, $dba_classes_by_mgdschema)) {
168 5
            return $dba_classes_by_mgdschema[$classname];
169
        }
170
171 1
        debug_add("{$classname} cannot be resolved to any DBA class name");
172 1
        $dba_classes_by_mgdschema[$classname] = false;
173 1
        return false;
174
    }
175
176
    /**
177
     * Get an MgdSchema class name for a MidCOM DBA class name
178
     *
179
     * @param string $classname The MidCOM DBA classname to check
180
     * @return string The corresponding MidCOM DBA class name, false otherwise.
181
     */
182 5
    public function get_mgdschema_class_name_for_midcom_class(string $classname)
183
    {
184 5
        static $mapping = [];
185
186 5
        if (!array_key_exists($classname, $mapping)) {
187 4
            $mapping[$classname] = false;
188
189 4
            if (class_exists($classname)) {
190 4
                $dummy_object = new $classname();
191 4
                if (!$this->is_midcom_db_object($dummy_object)) {
192
                    return false;
193
                }
194 4
                $mapping[$classname] = $dummy_object->__mgdschema_class_name__;
195
            }
196
        }
197
198 5
        return $mapping[$classname];
199
    }
200
201
    /**
202
     * Simple helper to check whether we are dealing with a MidCOM Database object
203
     * or a subclass thereof.
204
     *
205
     * @param object|string $object The object (or classname) to check
206
     */
207 513
    public function is_midcom_db_object($object) : bool
208
    {
209 513
        if (is_object($object)) {
210 513
            return ($object instanceof midcom_core_dbaobject || $object instanceof midcom_core_dbaproxy);
211
        }
212
        if (is_string($object) && class_exists($object)) {
213
            return $this->is_midcom_db_object(new $object);
214
        }
215
216
        return false;
217
    }
218
219 9
    public function get_component_classes(string $component) : array
220
    {
221 9
        $map = midcom::get()->componentloader->get_manifest($component)->class_mapping;
222 9
        if ($component == 'midcom') {
223
            $map[midcom::get()->config->get('person_class')] = midcom_db_person::class;
224
        }
225 9
        return $map;
226
    }
227
}
228