Completed
Push — 7.x-1.x ( 027296...66a87c )
by Frédéric G.
05:26
created

Memcache::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Drupal\qa\Plugin\Qa\Control\Cache;
4
5
use Drupal\qa\Pass;
6
use Drupal\qa\Plugin\Qa\Control\BaseControl;
7
8
/**
9
 * A QA Plugin for Memcache status over the Memcached (not Memcache) extension.
10
 *
11
 * @copyright Copyright (C) 2019 Frederic G. MARAND for Ouest Systèmes Informatiques (OSInet)
12
 *
13
 * @since DRUPAL-7
14
 *
15
 * @license Licensed under the disjunction of the CeCILL, version 2 and General Public License version 2 and later
16
 */
17
class Memcache extends BaseControl {
18
  const EXTENSION = 'memcached';
19
20
  /**
21
   * The subset of $conf possibly relevant to Memcache.
22
   *
23
   * @var array
24
   */
25
  const MEMCACHE_KEYS = [
26
    'cache_backends',
27
    'cache_class_cache_form',
28
    'cache_default_class',
29
    'lock_inc',
30
    'memcache_bins',
31
    'memcache_extension',
32
    'memcache_key_prefix',
33
    'memcache_servers',
34
    'memcache_stampede_protection',
35
    'memcache_stampede_semaphore',
36
    'memcache_stampede_wait_limit',
37
    'memcache_stampede_wait_time',
38
    'page_cache_invoke_hooks',
39
    'page_cache_without_database',
40
  ];
41
42
  protected $settings;
43
44
  /**
45
   * The Memcache plugin constructor.
46
   */
47
  public function __construct() {
48
    $this->settings = array_intersect_key($GLOBALS['conf'],
49
      array_combine(self::MEMCACHE_KEYS, self::MEMCACHE_KEYS));
50
51
    // Ancestor constructor invokes init() so call it last.
52
    parent::__construct();
53
  }
54
55
  /**
56
   * Initializer: must be implemented in concrete classes.
57
   *
58
   * Assignment to $this->package_name cannot be factored because it uses a
59
   * per-class magic constant.
60
   */
61
  public function init() {
62
    $this->package_name = __NAMESPACE__;
63
    $this->title = t('Check state of Memcached servers');
64
    $this->description = t('Validates actual connectivity and current miss and eviction rates. Assumes a correct settings configuration and the memcache module being installed, although not necessarily enabled.');
65
  }
66
67
  /**
68
   * {@inheritdoc}
69
   */
70
  public static function getDependencies() {
71
    $ret = parent::getDependencies();
72
    $ret = array_merge($ret, ['memcache']);
73
    return $ret;
74
  }
75
76
  /**
77
   * Check connectivity to a given Drupal cache bin in Memcached.
78
   *
79
   * @param string $bin
80
   *   The bin to check.
81
   * @param string $cluster
82
   *   The name of the cluster to which the bin belongs.
83
   *
84
   * @return array
85
   *   A "check" array (name, status, result)
86
   */
87
  protected function checkBin(string $bin, string $cluster): array {
0 ignored issues
show
Unused Code introduced by
The parameter $cluster is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
88
    $mc = dmemcache_object($bin);
89
    if ($mc === FALSE) {
90
      return [
91
        'name' => $bin,
92
        'status' => 0,
93
        'result' => 'Failed obtaining Memcached object',
94
      ];
95
    }
96
97
    // Contrary to documentation on
98
    // https://www.php.net/manual/fr/memcached.getstats.php , this function
99
    // returns FALSE if it cannot connect, instead of always returning an array.
100
    // @see https://bugs.php.net/bug.php?id=77809
101
    /** @var \Memcache|\Memcached $mc */
102
    $stats = $mc->getStats();
103
    $resultCode = $mc->getResultCode();
0 ignored issues
show
Bug introduced by
The method getResultCode does only exist in Memcached, but not in Memcache.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
104
    if ($resultCode !== 0 || !is_array($stats)) {
105
      return [
106
        'name' => $bin,
107
        'status' => 0,
108
        'result' => t('Error @code: @message', [
109
          '@code' => $resultCode,
110
          '@message' => $mc->getResultMessage(),
0 ignored issues
show
Bug introduced by
The method getResultMessage does only exist in Memcached, but not in Memcache.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
111
        ]),
112
      ];
113
    }
114
115
    return [
116
      'name' => $bin,
117
      'status' => 0,
118
      'result' => "ok",
119
    ];
120
  }
121
122
  /**
123
   * Check the configured bins.
124
   *
125
   * @param \Drupal\qa\Pass $pass
126
   *   The current control pass.
127
   *
128
   * @return \Drupal\qa\Pass
129
   *   The updated control pass.
130
   */
131
  protected function checkBins(Pass $pass): Pass {
132
    module_load_include('inc', 'memcache', 'dmemcache');
133
134
    foreach ($this->settings['memcache_bins'] as $bin => $cluster) {
135
      $pass->record($this->checkBin($bin, $cluster));
136
    }
137
    $pass->life->end();
138
139
    return $pass;
140
  }
141
142
  /**
143
   * Accumulate and format the results of the control pass.
144
   *
145
   * @param \Drupal\qa\Pass $pass
146
   *   The current control pass.
147
   *
148
   * @return \Drupal\qa\Pass
149
   *   The updated control pass.
150
   *
151
   * @throws \Exception
152
   */
153
  protected function finalizeBins(Pass $pass): Pass {
154
    $ok = theme('image', [
155
      'path' => 'misc/watchdog-ok.png',
156
      'alt' => t('OK'),
157
    ]);
158
    array_walk($pass->result, function (&$res, $bin) use ($ok) {
159
      if ($res === 'ok') {
160
        $res = $ok;
161
      }
162
      $res = [$bin, $res];
163
    });
164
    $result = [
165
      '#theme' => 'table',
166
      '#header' => [t('Bin'), t('Status')],
167
      '#rows' => $pass->result,
168
    ];
169
    $pass->result = drupal_render($result);
170
    return $pass;
171
  }
172
173
  /**
174
   * Format the results of a pre-control requirements check.
175
   *
176
   * @param \Drupal\qa\Pass $pass
177
   *   The current control pass.
178
   *
179
   * @return \Drupal\qa\Pass
180
   *   The updated control pass.
181
   */
182
  protected function finalizeRequirements(Pass $pass): Pass {
183
    $pass->life->end();
184
    // Now format result.
185
    $pass->result = implode($pass->result);
186
    return $pass;
187
  }
188
189
  /**
190
   * {@inheritdoc}
191
   */
192
  public function run(): Pass {
193
    $pass = parent::run();
194
    if (!extension_loaded(self::EXTENSION)) {
195
      $pass->record([
196
        'name' => 'memcached extension available',
197
        'status' => 0,
198
        'result' => 'Memcached extension not loaded',
199
      ]);
200
      return $this->finalizeRequirements($pass);
201
    }
202
203
    $ext = drupal_strtolower($this->settings['memcache_extension'] ?? self::EXTENSION);
204
    if ($ext !== self::EXTENSION) {
205
      $pass->record([
206
        'name' => 'memcached extension configured',
207
        'status' => 0,
208
        'result' => t('@expected extension loaded, but @actual configured instead', [
209
          '@expected' => drupal_strtolower(self::EXTENSION),
210
          '@actual' => $ext,
211
        ]),
212
      ]);
213
      return $this->finalizeRequirements($pass);
214
    }
215
216
    $pass = $this->checkBins($pass);
217
    return $this->finalizeBins($pass);
218
  }
219
220
}
221