Completed
Pull Request — develop (#303)
by
unknown
10:41
created

FileCache::createDirectoryRecursively()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 20
rs 9.4285
c 1
b 0
f 0
cc 3
eloc 11
nc 3
nop 1
1
<?php
2
/**
3
 * YAWIK
4
 *
5
 * @filesource
6
 * @license    MIT
7
 * @copyright  2013 - 2016 Cross Solution <http://cross-solution.de>
8
 */
9
namespace Organizations\Image;
10
11
use Zend\Mvc\MvcEvent;
12
use Zend\Mvc\Application;
13
use Zend\Http\Response;
14
use Zend\Http\Headers;
15
use Zend\Http\Response\Stream;
16
use Doctrine\Common\EventSubscriber;
17
use Doctrine\ODM\MongoDB\Event\PreUpdateEventArgs;
18
use Doctrine\ODM\MongoDB\Event\PostFlushEventArgs;
19
use Doctrine\ODM\MongoDB\Events;
20
use Organizations\Entity\Organization;
21
use Organizations\Entity\OrganizationImage;
22
use Zend\Stdlib\ErrorHandler;
23
24
/**
25
 * This class provides caching of organization images to file system using event listeners
26
 *
27
 * @author Miroslav Fedeleš <[email protected]>
28
 * @since 0.28
29
 */
30
class FileCache implements EventSubscriber
31
{
32
    
33
    /**
34
     * @var string
35
     */
36
    protected $filePath;
37
    
38
    /**
39
     * @var string
40
     */
41
    protected $uriPath;
42
    
43
    /**
44
     * @var array
45
     */
46
    protected $delete = [];
47
    
48
    /**
49
     * @param string $filePath
50
     * @param string $uriPath
51
     */
52
    public function __construct($filePath, $uriPath)
53
    {
54
        $this->filePath = $filePath;
55
        $this->uriPath = $uriPath;
56
    }
57
58
    /**
59
     * @param OrganizationImage $image
60
     * @return string
61
     */
62
    public function getUri(OrganizationImage $image)
63
    {
64
        return sprintf('%s/%s', $this->uriPath, $this->getImageSubPath($image));
65
    }
66
    
67
    /**
68
     * {@inheritDoc}
69
     * @see \Doctrine\Common\EventSubscriber::getSubscribedEvents()
70
     */
71
    public function getSubscribedEvents()
72
    {
73
        return [
74
            Events::preUpdate,
75
            Events::postFlush
76
        ];
77
    }
78
    
79
    /**
80
     * Creates and injects the organization reference to an user entity.
81
     *
82
     * @param LifecycleEventArgs $args
0 ignored issues
show
Bug introduced by
There is no parameter named $args. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
83
     */
84
    public function preUpdate(PreUpdateEventArgs $eventArgs)
85
    {
86
        $organization = $eventArgs->getDocument();
87
        
88
        // check for a organization instance
89
        if (! $organization instanceof Organization) {
90
            return;
91
        }
92
        
93
        // check if the image has been changed
94
        if (! $eventArgs->hasChangedField('image')) {
95
            return;
96
        }
97
        
98
        $image = $eventArgs->getOldValue('image');
99
        
100
        // check if any image existed
101
        if (! $image instanceof OrganizationImage) {
102
            return;
103
        }
104
        
105
        // mark image for deletion
106
        $this->delete[] = $image;
107
    }
108
109
    /**
110
     * @param LifecycleEventArgs $eventArgs
0 ignored issues
show
Documentation introduced by
Should the type for parameter $eventArgs not be PostFlushEventArgs?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
111
     */
112
    public function postFlush(PostFlushEventArgs $eventArgs)
0 ignored issues
show
Unused Code introduced by
The parameter $eventArgs 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...
113
    {
114
        // delete images from file system
115
        foreach ($this->delete as $image) {
116
            @unlink($this->getImagePath($image));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
117
        }
118
    }
119
    
120
    /**
121
     * @param MvcEvent $event
122
     */
123
    public function onDispatchError(MvcEvent $event)
124
    {
125
        if (Application::ERROR_ROUTER_NO_MATCH != $event->getError()) {
126
            // ignore other than 'no route' errors
127
            return;
128
        }
129
        
130
        // try match uri pattern
131
        $uri = $event->getRequest()->getRequestUri();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Zend\Stdlib\RequestInterface as the method getRequestUri() does only exist in the following implementations of said interface: Zend\Http\PhpEnvironment\Request.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
132
        $pattern = '#^' . preg_quote($this->uriPath, '#') . '/[0-9a-z]/[0-9a-z]/([0-9a-z]+)\.[a-zA-Z]{3,4}$#';
133
        $matches = [];
134
        preg_match($pattern, $uri, $matches);
135
        
136
        if (! isset($matches[1])) {
137
            // uri does not match organization image path
138
            return;
139
        }
140
        
141
        // try get image
142
        $id = $matches[1];
143
        $serviceManager = $event->getApplication()->getServiceManager();
144
        $repository = $serviceManager->get('repositories')->get('Organizations/OrganizationImage');
145
        $image = $repository->find($id);
146
        
147
        if (! $image) {
148
            // abort if image does not exist
149
            return;
150
        }
151
        
152
        $resource = $image->getResource();
153
        $path = $this->getImagePath($image);
154
        
155
        // create directory(ies)
156
        $this->createDirectoryRecursively(dirname($path));
157
        
158
        // store image
159
        file_put_contents($path, $resource);
160
        rewind($resource);
161
        
162
        // return image in response as a stream
163
        $headers = new Headers();
164
        $headers->addHeaders([
165
            'Content-Type' => $image->getType(),
166
            'Content-Length' => $image->getLength()
167
        ]);
168
        $response = new Stream();
169
        $response->setStream($resource);
170
        $response->setStatusCode(Response::STATUS_CODE_200);
171
        $response->setStreamName($image->getName());
172
        $response->setHeaders($headers);
173
        $event->setResponse($response);
174
    }
175
    
176
    /**
177
     * @param OrganizationImage $image
178
     * @return string
179
     */
180
    protected function getImagePath(OrganizationImage $image)
181
    {
182
        return sprintf('%s/%s', $this->filePath, $this->getImageSubPath($image));
183
    }
184
    
185
    /**
186
     * @param OrganizationImage $image
187
     * @return string
188
     */
189
    protected function getImageSubPath(OrganizationImage $image)
190
    {
191
        $id = $image->getId();
192
        $firstLevel = substr($id, -1);
193
        $secondLevel = substr($id, -2, 1);
194
        
195
        return sprintf('%s/%s/%s.%s', $firstLevel, $secondLevel, $id, pathinfo($image->getName(), PATHINFO_EXTENSION));
196
    }
197
198
    /**
199
     * @param string $dir
200
     */
201
    protected function createDirectoryRecursively($dir)
202
    {
203
        $dir = rtrim($dir, '/\\');
204
        
205
        if (! is_dir($dir)) {
206
            $this->createDirectoryRecursively(dirname($dir));
207
            
208
            $oldUmask = umask(0);
209
            
210
            ErrorHandler::start();
211
            $created = mkdir($dir, 0775);
212
            $error = ErrorHandler::stop();
213
            
214
            if (!$created) {
215
                throw new \RuntimeException(sprintf('unable to create directory "%s"', $dir), 0, $error);
216
            }
217
            
218
            umask($oldUmask);
219
        }
220
    }
221
}
222