Passed
Push — master ( 7a12e8...482ae9 )
by Andreas
23:08
created

midcom_services_cache::invalidate()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.0961

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 10
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 15
ccs 9
cts 11
cp 0.8182
crap 4.0961
rs 9.9332
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 Symfony\Component\EventDispatcher\EventSubscriberInterface;
10
use midcom\events\dbaevent;
11
use midgard\portable\storage\connection;
12
use Symfony\Component\Filesystem\Filesystem;
13
14
/**
15
 * This class is the central access point for all registered caching services. Currently
16
 * this includes the NAP, Memcache, Content and PHPscripts cache databases.
17
 *
18
 * The system is twofold:
19
 *
20
 * There are cache backends, which are responsible for the actual storage and retrieval of
21
 * cache information, and cache modules, which provide caching services to the application
22
 * developer.
23
 *
24
 * Check the documentation of the backends for configuring the cache on your live site
25
 * (as an administrator).
26
 *
27
 * Check the documentation of the cache modules to learn how to take advantage of the cache
28
 * services available (as a component/core author).
29
 *
30
 * The cache service is independent from the MidCOM Core, as it has to be started up at the
31
 * beginning of the request. Cache modules are loaded on-demand.
32
 *
33
 * This class will be available throughout he midcom service getter under the handle cache.
34
 * The content cache module, for backwards compatibility, will be available as $midcom->cache.
35
 *
36
 * All loaded modules will also be available as direct members of this class, you have to ensure
37
 * the module is loaded in advance though. The class will automatically load all modules which
38
 * are configured in the autoload_queue in the cache configuration.
39
 *
40
 * @property midcom_services_cache_module_content $content
41
 * @package midcom.services
42
 */
43
class midcom_services_cache implements EventSubscriberInterface
44
{
45
    /**
46
     * List of all loaded modules, indexed by their class name.
47
     *
48
     * @var midcom_services_cache_module[]
49
     */
50
    private $_modules = [];
51
52
    /**
53
     * Cache service startup. It initializes all cache modules configured in the
54
     * global configuration as outlined in the class introduction.
55
     *
56
     * It will load the content cache module as the first one, the rest will be
57
     * loaded in their order of appearance in the Array.
58
     */
59
    public function __construct()
60
    {
61
        array_map([$this, 'load_module'], midcom::get()->config->get('cache_autoload_queue'));
0 ignored issues
show
Bug introduced by
It seems like midcom::get()->config->g...'cache_autoload_queue') can also be of type null; however, parameter $arr1 of array_map() does only seem to accept array, 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

61
        array_map([$this, 'load_module'], /** @scrutinizer ignore-type */ midcom::get()->config->get('cache_autoload_queue'));
Loading history...
62
        midcom::get()->dispatcher->addSubscriber($this);
63
    }
64
65
    public static function getSubscribedEvents()
66
    {
67
        return [
68
            dbaevent::CREATE => ['handle_create'],
69
            dbaevent::UPDATE => ['handle_update'],
70
            dbaevent::DELETE => ['handle_event'],
71
            dbaevent::APPROVE => ['handle_event'],
72
            dbaevent::UNAPPROVE => ['handle_event'],
73
        ];
74
    }
75
76 184
    public function handle_event(dbaevent $event)
77
    {
78 184
        $object = $event->get_object();
79 184
        $this->invalidate($object);
80 184
    }
81
82 282
    public function handle_create(dbaevent $event)
83
    {
84 282
        if ($parent = $event->get_object()->get_parent()) {
85
            // Invalidate parent from cache so content caches have chance to react
86 168
            $this->invalidate($parent);
87
        }
88 282
    }
89
90 115
    public function handle_update(dbaevent $event)
91
    {
92 115
        $object = $event->get_object();
93 115
        $this->invalidate($object);
94
95 115
        if (midcom::get()->config->get('attachment_cache_enabled')) {
96
            foreach ($object->list_attachments() as $att) {
97
                $this->invalidate($att->guid);
98
                // This basically ensures that attachment cache links are
99
                // deleted when their parent is no longer readable by everyone
100
                // @todo: The only question is: Does it even get triggered on privilege changes?
101
                $att->update_cache();
102
            }
103
        }
104 115
    }
105
106
    /**
107
     * Load the specified cache module (if not already loaded), add it to the _modules array
108
     * and assign it to a member variable named after the module.
109
     *
110
     * @param string $name The name of the cache module to load.
111
     */
112
    private function load_module(string $name)
113
    {
114
        if (isset($this->_modules[$name])) {
115
            return;
116
        }
117
118
        $classname = "midcom_services_cache_module_{$name}";
119
120
        if (!class_exists($classname)) {
121
            throw new midcom_error("Tried to load the cache module {$name}, but the class {$classname} was not found");
122
        }
123
124
        $this->_modules[$name] = new $classname();
125
        $this->_modules[$name]->initialize();
126
        $this->$name =& $this->_modules[$name];
127
    }
128
129
    /**
130
     * Invalidate all caches completely.
131
     *
132
     * Use this, if you have, f.x. changes in the layout. The URL function
133
     * midcom-cache-invalidate will trigger this function.
134
     */
135
    public function invalidate_all()
136
    {
137
        foreach ($this->_modules as $name => $module) {
138
            debug_add("Invalidating the cache module {$name} completely.");
139
            $module->invalidate_all();
140
        }
141
        $fs = new Filesystem;
142
        $fs->remove([midcom::get()->getCacheDir()]);
143
        // see https://github.com/symfony/symfony/pull/36540
144
        if (function_exists('opcache_reset')) {
145
            opcache_reset();
146
        }
147
148
        connection::invalidate_cache();
149
    }
150
151
    /**
152
     * Invalidates all cache records associated with a given content object.
153
     *
154
     * @param mixed $guid This is either a GUID or a MidgardObject, in which case the Guid is auto-determined.
155
     */
156 288
    public function invalidate($guid)
157
    {
158 288
        $object = null;
159 288
        if (is_object($guid)) {
160 245
            $object = $guid;
161 245
            $guid = $object->guid;
162
        }
163 288
        if (empty($guid)) {
164
            debug_add("Called for empty GUID, ignoring invalidation request.");
165
            return;
166
        }
167
168 288
        foreach ($this->_modules as $name => $module) {
169 288
            debug_add("Invalidating the cache module {$name} for GUID {$guid}.");
170 288
            $module->invalidate($guid, $object);
171
        }
172 288
    }
173
}
174