CloudFileExtension   F
last analyzed

Complexity

Total Complexity 67

Size/Duplication

Total Lines 362
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 67
lcom 1
cbo 8
dl 0
loc 362
rs 3.04
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
B onBeforeWrite() 0 28 6
A onAfterWrite() 0 4 1
B onAfterDelete() 0 20 6
C updateCloudStatus() 0 75 16
A canBeInCloud() 0 10 3
A containsPlaceholder() 0 8 3
A convertToPlaceholder() 0 13 3
A getCloudBucket() 0 4 1
A getCloudURL() 0 5 2
A getCloudMeta() 0 13 5
A setCloudMeta() 0 12 2
A downloadFromCloud() 0 17 4
B createLocalIfNeeded() 0 22 8
A isCloudPutNeeded() 0 27 5
A isLocalMissing() 0 4 2

How to fix   Complexity   

Complex Class

Complex classes like CloudFileExtension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CloudFileExtension, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This is added to all File objects for shared functionality.
4
 *
5
 * @author Mark Guinn <[email protected]>
6
 * @date 01.10.2014
7
 * @package cloudassets
8
 */
9
class CloudFileExtension extends DataExtension
0 ignored issues
show
Complexity introduced by
This class has a complexity of 67 which exceeds the configured maximum of 50.

The class complexity is the sum of the complexity of all methods. A very high value is usually an indication that your class does not follow the single reponsibility principle and does more than one job.

Some resources for further reading:

You can also find more detailed suggestions for refactoring in the “Code” section of your repository.

Loading history...
10
{
11
    private static $db = array(
12
        'CloudStatus'   => "Enum('Local,Live,Error','Local')",
13
        'CloudSize'     => 'Int',
14
        'CloudMetaJson' => 'Text',      // saves any bucket or file-type specific information
15
    );
16
17
    /** @var File|CloudFileExtension */
18
    protected $owner;
19
20
    private $inUpdate = false;
21
22
23
    /**
24
     * Handle renames
25
     */
26
    public function onBeforeWrite()
27
    {
28
        $bucket = CloudAssets::inst()->map($this->owner->getFilename());
0 ignored issues
show
Bug introduced by
The method getFilename does only exist in File, but not in CloudFileExtension.

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...
29
        if ($bucket) {
30
            if (!$this->owner->isChanged('Filename')) {
0 ignored issues
show
Bug introduced by
The method isChanged does only exist in File, but not in CloudFileExtension.

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...
31
                return;
32
            }
33
34
            $changedFields = $this->owner->getChangedFields();
0 ignored issues
show
Bug introduced by
The method getChangedFields does only exist in File, but not in CloudFileExtension.

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...
35
            $pathBefore = $changedFields['Filename']['before'];
36
            $pathAfter = $changedFields['Filename']['after'];
37
38
            // If the file or folder didn't exist before, don't rename - its created
39
            if (!$pathBefore) {
40
                return;
41
            }
42
43
            // Tell the remote to rename the file (or delete and recreate or whatever)
44
            if ($this->owner->hasMethod('onBeforeCloudRename')) {
0 ignored issues
show
Bug introduced by
The method hasMethod does only exist in File, but not in CloudFileExtension.

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...
45
                $this->owner->onBeforeCloudRename($pathBefore, $pathAfter);
46
            }
47
            CloudAssets::inst()->getLogger()->info("CloudAssets: Renaming $pathBefore to $pathAfter");
48
            $bucket->rename($this->owner, $pathBefore, $pathAfter);
0 ignored issues
show
Bug introduced by
It seems like $this->owner can also be of type object<CloudFileExtension>; however, CloudBucket::rename() does only seem to accept object<File>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
49
            if ($this->owner->hasMethod('onAfterCloudRename')) {
50
                $this->owner->onAfterCloudRename($pathBefore, $pathAfter);
51
            }
52
        }
53
    }
54
55
56
    /**
57
     * Update cloud status any time the file is written
58
     */
59
    public function onAfterWrite()
60
    {
61
        $this->updateCloudStatus();
62
    }
63
64
65
    /**
66
     * Delete the file from the cloud (if it was ever there)
67
     */
68
    public function onAfterDelete()
69
    {
70
        $bucket = CloudAssets::inst()->map($this->owner->getFilename());
0 ignored issues
show
Bug introduced by
The method getFilename does only exist in File, but not in CloudFileExtension.

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...
71
        if ($bucket && !Config::inst()->get('CloudAssets', 'uploads_disabled')) {
72
            if ($this->owner->hasMethod('onBeforeCloudDelete')) {
0 ignored issues
show
Bug introduced by
The method hasMethod does only exist in File, but not in CloudFileExtension.

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...
73
                $this->owner->onBeforeCloudDelete();
74
            }
75
76
            try {
77
                CloudAssets::inst()->getLogger()->info("CloudAssets: deleting {$this->owner->getFilename()}");
78
                $bucket->delete($this->owner);
0 ignored issues
show
Bug introduced by
It seems like $this->owner can also be of type object<CloudFileExtension>; however, CloudBucket::delete() does only seem to accept object<File>|string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
79
            } catch (Exception $e) {
80
                CloudAssets::inst()->getLogger()->error("CloudAssets: Failed bucket delete: " . $e->getMessage() . " for " . $this->owner->getFullPath());
0 ignored issues
show
Bug introduced by
The method getFullPath does only exist in File, but not in CloudFileExtension.

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...
81
            }
82
83
            if ($this->owner->hasMethod('onAfterCloudDelete')) {
84
                $this->owner->onAfterCloudDelete();
85
            }
86
        }
87
    }
88
89
90
    /**
91
     * Performs two functions:
92
     * 1. Wraps this object in CloudFile (etc) by changing the classname if it should be and is not
93
     * 2. Uploads the file to the cloud storage if it doesn't contain the placeholder
94
     *
95
     * @return File
96
     */
97
    public function updateCloudStatus()
98
    {
99
        if ($this->inUpdate) {
100
            return;
101
        }
102
        $this->inUpdate = true;
103
        $cloud  = CloudAssets::inst();
104
105
        // does this file fall under a cloud bucket?
106
        $bucket = $cloud->map($this->owner->getFilename());
0 ignored issues
show
Bug introduced by
The method getFilename does only exist in File, but not in CloudFileExtension.

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...
107
        if ($bucket) {
108
            // does this file need to be wrapped?
109
            $wrapClass = $cloud->getWrapperClass($this->owner->ClassName);
110
            if (!empty($wrapClass)) {
111
                if ($wrapClass != $this->owner->ClassName) {
112
                    $cloud->getLogger()->debug("CloudAssets: wrapping {$this->owner->ClassName} to $wrapClass. ID={$this->owner->ID}");
113
                    $this->owner->ClassName = $wrapClass;
114
                    $this->owner->write();
0 ignored issues
show
Bug introduced by
The method write does only exist in File, but not in CloudFileExtension.

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...
115
                    $wrapped = DataObject::get($wrapClass)->byID($this->owner->ID);
116
                    if ($wrapped->hasMethod('onAfterCloudWrap')) {
117
                        $wrapped->onAfterCloudWrap();
118
                    }
119
                } else {
120
                    $wrapped = $this->owner;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->owner; of type File|CloudFileExtension adds the type CloudFileExtension to the return on line 165 which is incompatible with the return type documented by CloudFileExtension::updateCloudStatus of type File|null.
Loading history...
121
                }
122
123
                // does this file need to be uploaded to storage?
124
                if ($wrapped->canBeInCloud() && $wrapped->isCloudPutNeeded() && !Config::inst()->get('CloudAssets', 'uploads_disabled')) {
0 ignored issues
show
Bug introduced by
The method canBeInCloud does only exist in CloudFileExtension, but not in DataObject.

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...
Bug introduced by
The method isCloudPutNeeded does only exist in CloudFileExtension, but not in DataObject.

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...
125
                    try {
126
                        if ($wrapped->hasMethod('onBeforeCloudPut')) {
0 ignored issues
show
Bug introduced by
The method hasMethod does only exist in DataObject, but not in CloudFileExtension.

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...
127
                            $wrapped->onBeforeCloudPut();
128
                        }
129
                        $cloud->getLogger()->debug("CloudAssets: uploading file ".$wrapped->getFilename());
130
                        $bucket->put($wrapped);
0 ignored issues
show
Documentation introduced by
$wrapped is of type object<DataObject>|null|...ect<CloudFileExtension>, but the function expects a object<File>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
131
132
                        $wrapped->setCloudMeta('LastPut', time());
0 ignored issues
show
Bug introduced by
The method setCloudMeta does only exist in CloudFileExtension, but not in DataObject.

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...
133
                        $wrapped->CloudStatus = 'Live';
134
                        $wrapped->CloudSize   = filesize($this->owner->getFullPath());
0 ignored issues
show
Bug introduced by
The method getFullPath does only exist in File, but not in CloudFileExtension.

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...
135
                        $wrapped->write();
0 ignored issues
show
Bug introduced by
The method write does only exist in DataObject, but not in CloudFileExtension.

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...
136
137
                        $wrapped->convertToPlaceholder();
0 ignored issues
show
Bug introduced by
The method convertToPlaceholder does only exist in CloudFileExtension, but not in DataObject.

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...
138
                        if ($wrapped->hasMethod('onAfterCloudPut')) {
139
                            $wrapped->onAfterCloudPut();
140
                        }
141
                    } catch (Exception $e) {
142
                        $wrapped->CloudStatus = 'Error';
143
                        $wrapped->write();
144
                        $cloud->getLogger()->error("CloudAssets: Failed bucket upload: " . $e->getMessage() . " for " . $wrapped->getFullPath());
145
                        // Fail silently for now. This will cause the local copy to be served.
146
                    }
147
                } elseif ($wrapped->CloudStatus !== 'Live' && $wrapped->containsPlaceholder()) {
0 ignored issues
show
Bug introduced by
The method containsPlaceholder does only exist in CloudFileExtension, but not in DataObject.

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...
148
                    // If this is a duplicate file, update the status
149
                    // This shouldn't happen ever and won't happen often but when it does this will be helpful
150
                    $dup = File::get()->filter(array(
151
                        'Filename'      => $wrapped->Filename,
152
                        'CloudStatus'   => 'Live',
153
                    ))->first();
154
155
                    if ($dup && $dup->exists()) {
156
                        $cloud->getLogger()->warn("CloudAssets: fixing status for duplicate file: {$wrapped->ID} and {$dup->ID}");
157
                        $wrapped->CloudStatus   = $dup->CloudStatus;
158
                        $wrapped->CloudSize     = $dup->CloudSize;
159
                        $wrapped->CloudMetaJson = $dup->CloudMetaJson;
160
                        $wrapped->write();
161
                    }
162
                }
163
164
                $this->inUpdate = false;
165
                return $wrapped;
166
            }
167
        }
168
169
        $this->inUpdate = false;
170
        return $this->owner;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->owner; of type File|CloudFileExtension adds the type CloudFileExtension to the return on line 170 which is incompatible with the return type documented by CloudFileExtension::updateCloudStatus of type File|null.
Loading history...
171
    }
172
173
174
    /**
175
     * @return bool
176
     */
177
    public function canBeInCloud()
178
    {
179
        if ($this->owner instanceof Folder) {
180
            return false;
181
        }
182
        if (!file_exists($this->owner->getFullPath())) {
0 ignored issues
show
Bug introduced by
The method getFullPath does only exist in File, but not in CloudFileExtension.

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...
183
            return false;
184
        }
185
        return true;
186
    }
187
188
189
    /**
190
     * @return bool
191
     */
192
    public function containsPlaceholder()
193
    {
194
        $placeholder = Config::inst()->get('CloudAssets', 'file_placeholder');
195
        $path = $this->owner->getFullPath();
0 ignored issues
show
Bug introduced by
The method getFullPath does only exist in File, but not in CloudFileExtension.

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...
196
197
        // check the size first to avoid reading crazy huge files into memory
198
        return (file_exists($path) && filesize($path) == strlen($placeholder) && file_get_contents($path) == $placeholder);
199
    }
200
201
202
    /**
203
     * Wipes out the contents of this file and replaces with placeholder text
204
     */
205
    public function convertToPlaceholder()
206
    {
207
        $bucket = $this->getCloudBucket();
208
        if ($bucket && !$bucket->isLocalCopyEnabled()) {
209
            $path = $this->owner->getFullPath();
0 ignored issues
show
Bug introduced by
The method getFullPath does only exist in File, but not in CloudFileExtension.

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...
210
            CloudAssets::inst()->getLogger()->debug("CloudAssets: converting $path to placeholder");
211
            Filesystem::makeFolder(dirname($path));
212
            file_put_contents($path, Config::inst()->get('CloudAssets', 'file_placeholder'));
213
            clearstatcache();
214
        }
215
216
        return $this->owner;
217
    }
218
219
220
    /**
221
     * @return CloudBucket
222
     */
223
    public function getCloudBucket()
224
    {
225
        return CloudAssets::inst()->map($this->owner);
0 ignored issues
show
Bug introduced by
It seems like $this->owner can also be of type object<CloudFileExtension>; however, CloudAssets::map() does only seem to accept string|object<File>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
226
    }
227
228
229
    /**
230
     * @param int $linkType [optional] - see CloudBucket::LINK_XXX constants
231
     * @return string
232
     */
233
    public function getCloudURL($linkType = CloudBucket::LINK_SMART)
234
    {
235
        $bucket = $this->getCloudBucket();
236
        return $bucket ? $bucket->getLinkFor($this->owner, $linkType) : '';
0 ignored issues
show
Bug introduced by
It seems like $this->owner can also be of type object<CloudFileExtension>; however, CloudBucket::getLinkFor() does only seem to accept object<File>|string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
237
    }
238
239
240
    /**
241
     * @param string $key [optional] - if not present returns the whole array
242
     * @return array
243
     */
244
    public function getCloudMeta($key = null)
245
    {
246
        $data = json_decode($this->owner->CloudMetaJson, true);
247
        if (empty($data) || !is_array($data)) {
248
            $data = array();
249
        }
250
251
        if (!empty($key)) {
252
            return isset($data[$key]) ? $data[$key] : null;
253
        } else {
254
            return $data;
255
        }
256
    }
257
258
259
    /**
260
     * @param string|array $key - passing an array as the first argument replaces the meta data entirely
261
     * @param mixed        $val
262
     * @return File - chainable
263
     */
264
    public function setCloudMeta($key, $val = null)
265
    {
266
        if (is_array($key)) {
267
            $data = $key;
268
        } else {
269
            $data = $this->getCloudMeta();
270
            $data[$key] = $val;
271
        }
272
273
        $this->owner->CloudMetaJson = json_encode($data);
274
        return $this->owner;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->owner; of type File|CloudFileExtension adds the type CloudFileExtension to the return on line 274 which is incompatible with the return type documented by CloudFileExtension::setCloudMeta of type File.
Loading history...
275
    }
276
277
278
    /**
279
     * If this file is stored in the cloud, downloads the cloud
280
     * copy and replaces whatever is local.
281
     */
282
    public function downloadFromCloud()
283
    {
284
        if ($this->owner->CloudStatus === 'Live') {
285
            $bucket   = $this->owner->getCloudBucket();
0 ignored issues
show
Bug introduced by
The method getCloudBucket does only exist in CloudFileExtension, but not in File.

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...
286
            if ($bucket) {
287
                $contents = $bucket->getContents($this->owner);
288
                $path     = $this->owner->getFullPath();
0 ignored issues
show
Bug introduced by
The method getFullPath does only exist in File, but not in CloudFileExtension.

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...
289
                Filesystem::makeFolder(dirname($path));
290
                CloudAssets::inst()->getLogger()->debug("CloudAssets: downloading $path from cloud (size=".strlen($contents).")");
291
                // if there was an error and we overwrote the local file with empty or null, it could delete the remote
292
                // file as well. Better to err on the side of not writing locally when we should than that.
293
                if (!empty($contents)) {
294
                    file_put_contents($path, $contents);
295
                }
296
            }
297
        }
298
    }
299
300
301
    /**
302
     * If the file is present in the database and the cloud but not
303
     * locally, create a placeholder for it. This can happen in a lot
304
     * of cases such as load balanced servers and local development.
305
     */
306
    public function createLocalIfNeeded()
307
    {
308
        if ($this->owner->CloudStatus === 'Live') {
309
            $bucket = $this->getCloudBucket();
310
            if ($bucket && $bucket->isLocalCopyEnabled()) {
311
                if (!file_exists($this->owner->getFullPath()) || $this->containsPlaceholder()) {
312
                    try {
313
                        $this->downloadFromCloud();
314
                    } catch (Exception $e) {
315
                        // I'm not sure what the correct behaviour is here
316
                        // Pretty sure it'd be better to have a broken image
317
                        // link than a 500 error though.
318
                        CloudAssets::inst()->getLogger()->error("CloudAssets: Failed bucket download: " . $e->getMessage() . " for " . $this->owner->getFullPath());
0 ignored issues
show
Bug introduced by
The method getFullPath does only exist in File, but not in CloudFileExtension.

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...
319
                    }
320
                }
321
            } else {
322
                if (!file_exists($this->owner->getFullPath())) {
323
                    $this->convertToPlaceholder();
324
                }
325
            }
326
        }
327
    }
328
329
330
    /**
331
     * @return bool
332
     */
333
    public function isCloudPutNeeded()
334
    {
335
        // we never want to upload the placeholder
336
        if ($this->containsPlaceholder()) {
337
            return false;
338
        }
339
340
        // we never want to upload an empty file
341
        $path = $this->owner->getFullPath();
0 ignored issues
show
Bug introduced by
The method getFullPath does only exist in File, but not in CloudFileExtension.

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...
342
        if (!file_exists($path)) {
343
            return false;
344
        }
345
346
        // we always want to upload if it's the first time
347
        $lastPut = $this->getCloudMeta('LastPut');
348
        if (!$lastPut) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $lastPut of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
349
            return true;
350
        }
351
352
        // additionally, we want to upload if the file has been changed or replaced
353
        $mtime = filemtime($path);
354
        if ($mtime > $lastPut) {
355
            return true;
356
        }
357
358
        return false;
359
    }
360
361
362
    /**
363
     * Returns true if the local file is not available
364
     * @return bool
365
     */
366
    public function isLocalMissing()
367
    {
368
        return !file_exists($this->owner->getFullPath()) || $this->containsPlaceholder();
0 ignored issues
show
Bug introduced by
The method getFullPath does only exist in File, but not in CloudFileExtension.

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...
369
    }
370
}
371