Passed
Push — master ( e0d2b5...5f877a )
by Andreas
20:58
created

midcom_services_cache_module_nap::put_node()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 2
nop 2
dl 0
loc 11
ccs 9
cts 9
cp 1
crap 3
rs 10
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
/**
10
 * This is the NAP caching module. It provides the basic management functionality
11
 * for the various backend databases which will be created based on the root topic of the
12
 * NAP trees.
13
 *
14
 * The actual handling of the various dbs is done with Doctrine Cache, this
15
 * class is responsible for the creation of backend instances and invalidation for NAP
16
 * cache objects. (Which implies that it is fully aware of the data structures
17
 * stored in the cache.)
18
 *
19
 * All entries are indexed by their Midgard Object GUID. The entries in the NAP cache
20
 * basically resemble the arrays within the NAP backend node/leaf cache
21
 *
22
 * NAP caches can be shared over multiple sites, as all site specific data (like
23
 * site prefixes) are evaluated during runtime.
24
 *
25
 * Most of the cache update work is done in midcom_helper_nav_backend,
26
 * so you should look there for details about the caching strategy.
27
 *
28
 * @see midcom_helper_nav_backend
29
 *
30
 * @package midcom.services
31
 */
32
class midcom_services_cache_module_nap extends midcom_services_cache_module
33
{
34
    /**
35
     * {@inheritDoc}
36
     */
37 320
    public function invalidate(string $guid, midcom_core_dbaobject $object = null)
38
    {
39 320
        $napobject = $this->get_guid($guid);
40
41 320
        if (!$napobject) {
42
            // The object itself is not in cache, but it still might have a parent that
43
            // needs invalidating (f.x. if it is newly-created or was moved from outside the tree)
44 318
            $napobject = $this->_load_from_guid($guid, $object);
45 318
            if (!$napobject) {
46
                // We couldn't load the object (because it's deleted f.x.) or it is not in NAP.
47
                // Either way, there is nothing more we can do here.
48 292
                return;
49
            }
50
        }
51
52 111
        if ($napobject[MIDCOM_NAV_TYPE] == 'leaf') {
53 2
            $cached_node_id = $napobject[MIDCOM_NAV_NODEID];
54
            // Get parent from DB and compare to catch moves
55 2
            if ($parent = $napobject[MIDCOM_NAV_OBJECT]->get_parent()) {
56 2
                $parent_entry = $this->get_guid($parent->guid);
57 2
                if (   $parent_entry
58 2
                    && $parent_entry[MIDCOM_NAV_ID] != $cached_node_id) {
59
                    $this->backend->deleteItem($parent_entry[MIDCOM_NAV_ID] . '-leaves');
60
                }
61
            }
62 2
            if (!empty($napobject[MIDCOM_NAV_GUID])) {
63 2
                $this->backend->deleteItem($napobject[MIDCOM_NAV_GUID]);
64
            }
65
        } else {
66 111
            $cached_node_id = $napobject[MIDCOM_NAV_ID];
67
68
            //Invalidate subnode cache for the (cached) parent
69 111
            $parent_id = $napobject[MIDCOM_NAV_NODEID];
70 111
            $parent_entry = $this->get_node($parent_id);
71
72 111
            if (   $parent_entry
73 111
                && array_key_exists(MIDCOM_NAV_SUBNODES, $parent_entry)) {
74 1
                unset($parent_entry[MIDCOM_NAV_SUBNODES]);
75 1
                $this->put_node($parent_id, $parent_entry);
76
            }
77
78
            //Cross-check parent value from object to detect topic moves
79 111
            if ($parent = $napobject[MIDCOM_NAV_OBJECT]->get_parent()) {
80 27
                $parent_entry_from_object = $this->get_guid($parent->guid);
81
82 27
                if (    !empty($parent_entry_from_object[MIDCOM_NAV_ID])
83 27
                     && !empty($parent_entry[MIDCOM_NAV_ID])
84 27
                     && $parent_entry_from_object[MIDCOM_NAV_ID] != $parent_entry[MIDCOM_NAV_ID]) {
85
                    unset($parent_entry_from_object[MIDCOM_NAV_SUBNODES]);
86
                    $this->put_node($parent_entry_from_object[MIDCOM_NAV_ID], $parent_entry_from_object);
87
                }
88
            }
89
        }
90
91 111
        $leaves_key = "{$cached_node_id}-leaves";
92
93 111
        $this->backend->deleteItem((string) $cached_node_id);
94 111
        $this->backend->deleteItem($napobject[MIDCOM_NAV_GUID]);
95 111
        $this->backend->deleteItem($leaves_key);
96
    }
97
98 318
    private function _load_from_guid(string $guid, ?midcom_core_dbaobject $object) : ?array
99
    {
100
        try {
101 318
            $object ??= midcom::get()->dbfactory->get_object_by_guid($guid);
102 318
            $nav = new midcom_helper_nav;
103 318
            if ($object instanceof midcom_db_topic) {
104 103
                return $nav->get_node($object->id);
105
            }
106 268
            if (   ($node = $nav->find_closest_topic($object))
107 268
                && $nodeobject = $nav->get_node($node->id)) {
108 268
                return $nav->get_leaf($nodeobject[MIDCOM_NAV_ID] . '-' . $object->id);
109
            }
110
        } catch (midcom_error $e) {
111
            $e->log();
112
        }
113 247
        return null;
114
    }
115
116
    /**
117
     * Looks up a node in the cache and returns it. Not existent
118
     * keys are caught in this call as well, so you do not need
119
     * to call exists first.
120
     */
121 168
    public function get_node(string $key) : ?array
122
    {
123 168
        $lang_id = midcom::get()->i18n->get_current_language();
124 168
        return $this->backend->getItem($key)->get()[$lang_id] ?? null;
125
    }
126
127
    /**
128
     * Looks up a node in the cache and returns it. Not existent
129
     * keys are caught in this call as well, so you do not need
130
     * to call exists first.
131
     */
132 43
    public function get_leaves(string $key) : ?array
133
    {
134 43
        $lang_id = midcom::get()->i18n->get_current_language();
135 43
        return $this->backend->getItem($key)->get()[$lang_id] ?? null;
136
    }
137
138
    /**
139
     * Sets a given node key in the cache.
140
     *
141
     * @param mixed $data The data to store.
142
     */
143 123
    public function put_node(string $key, $data)
144
    {
145 123
        $item = $this->backend->getItem($key);
146 123
        $lang_id = midcom::get()->i18n->get_current_language();
147 123
        $result = $item->get() ?: [];
148 123
        $result[$lang_id] = $data;
149 123
        $this->backend->save($item->set($result));
150
        // symfony cache doesn't like empty cache keys
151 123
        if ($data[MIDCOM_NAV_GUID]) {
152 122
            $guid_item = $this->backend->getItem($data[MIDCOM_NAV_GUID]);
153 122
            $this->backend->save($guid_item->set($result));
154
        }
155
    }
156
157
    /**
158
     * Save a given array by GUID in the cache.
159
     *
160
     * @param mixed $data The data to store.
161
     */
162 1
    public function put_guid(string $guid, $data)
163
    {
164 1
        $lang_id = midcom::get()->i18n->get_current_language();
165 1
        $item = $this->backend->getItem($guid);
166 1
        $result = $item->get() ?: [];
167 1
        $result[$lang_id] = $data;
168 1
        $this->backend->save($item->set($result));
169
    }
170
171
    /**
172
     * Get a given array by GUID from the cache.
173
     */
174 327
    public function get_guid(string $guid) : ?array
175
    {
176 327
        $lang_id = midcom::get()->i18n->get_current_language();
177 327
        return $this->backend->getItem($guid)->get()[$lang_id] ?? null;
178
    }
179
180
    /**
181
     * Sets a given leave key in the cache
182
     *
183
     * @param mixed $data The data to store.
184
     */
185 8
    public function put_leaves(string $key, $data)
186
    {
187 8
        $lang_id = midcom::get()->i18n->get_current_language();
188 8
        $item = $this->backend->getItem($key);
189 8
        $result = $item->get() ?: [];
190 8
        $result[$lang_id] = $data;
191 8
        $this->backend->save($item->set($result));
192
    }
193
}
194