Test Failed
Push — master ( b7ab7b...94ee03 )
by Kirill
02:59
created

Compiler::wrap()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 13
ccs 0
cts 12
cp 0
crap 12
rs 9.8333
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of Railt package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
declare(strict_types=1);
9
10
namespace Railt\SDL;
11
12
use Psr\Log\LoggerAwareInterface;
13
use Psr\Log\LoggerAwareTrait;
14
use Psr\Log\LoggerInterface;
15
use Railt\Io\File;
16
use Railt\Io\Readable;
17
use Railt\Reflection\Contracts\Document;
18
use Railt\Reflection\Contracts\Document as DocumentInterface;
19
use Railt\Reflection\Reflection;
20
use Railt\SDL\Compiler\Dictionary;
21
use Railt\SDL\Compiler\Store;
22
use Railt\SDL\Exception\CompilerException;
23
use Railt\SDL\Exception\InternalException;
24
use Railt\SDL\Frontend\Record\RecordInterface;
25
26
/**
27
 * Class Compiler
28
 */
29
class Compiler implements LoggerAwareInterface, CompilerInterface
30
{
31
    use LoggerAwareTrait;
32
33
    /**
34
     * @var Dictionary
35
     */
36
    private $dictionary;
37
38
    /**
39
     * @var Store
40
     */
41
    private $store;
42
43
    /**
44
     * @var Frontend
45
     */
46
    private $front;
47
48
    /**
49
     * @var Backend
50
     */
51
    private $back;
52
53
    /**
54
     * Compiler constructor.
55
     * @param LoggerInterface|null $logger
56
     * @throws \Railt\Io\Exception\ExternalFileException
57
     * @throws \Railt\Reflection\Exception\TypeConflictException
58
     */
59
    public function __construct(LoggerInterface $logger = null)
60
    {
61
        $this->store      = new Store();
62
        $this->front      = new Frontend();
63
        $this->dictionary = new Dictionary($this);
64
        $this->back       = new Backend($this->front, new Reflection($this->dictionary));
65
66
        if ($logger) {
67
            $this->setLogger($logger);
68
        }
69
    }
70
71
    /**
72
     * @param LoggerInterface $logger
73
     * @return Compiler
74
     */
75
    public function setLogger(LoggerInterface $logger): self
76
    {
77
        $this->logger = $logger;
78
79
        $this->front->setLogger($logger);
80
        $this->back->setLogger($logger);
81
82
        return $this;
83
    }
84
85
    /**
86
     * @param \Closure $then
87
     * @return CompilerInterface|$this
88
     */
89
    public function autoload(\Closure $then): CompilerInterface
90
    {
91
        $this->dictionary->onTypeNotFound($then);
92
93
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Railt\SDL\Compiler) is incompatible with the return type declared by the interface Railt\SDL\CompilerInterface::autoload of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
94
    }
95
96
    /**
97
     * @param Readable $file
98
     * @return DocumentInterface
99
     */
100
    public function compile(Readable $file): DocumentInterface
101
    {
102
        return $this->store->memoize($file, function (Readable $file): DocumentInterface {
103
            return $this->generate($file, $this->ir($file));
104
        });
105
    }
106
107
    /**
108
     * @param Readable $readable
109
     * @param iterable $records
110
     * @return Document
111
     * @throws CompilerException
112
     * @throws InternalException
113
     * @throws \Railt\Io\Exception\NotReadableException
114
     */
115
    private function generate(Readable $readable, iterable $records): Document
116
    {
117
        return $this->wrap(function () use ($readable, $records) {
118
            return $this->back->run($readable, $records);
119
        });
120
    }
121
122
    /**
123
     * @param Readable $readable
124
     * @return iterable
125
     * @throws CompilerException
126
     * @throws InternalException
127
     * @throws \Railt\Io\Exception\NotReadableException
128
     */
129
    private function ir(Readable $readable): iterable
130
    {
131
        return $this->wrap(function () use ($readable) {
132
            return $this->front->load($readable);
133
        });
134
    }
135
136
    /**
137
     * @param \Closure $runner
138
     * @return mixed
139
     * @throws CompilerException
140
     * @throws InternalException
141
     * @throws \Railt\Io\Exception\NotReadableException
142
     */
143
    private function wrap(\Closure $runner)
144
    {
145
        try {
146
            return $runner();
147
        } catch (CompilerException $e) {
148
            throw $e;
149
        } catch (\Throwable $e) {
150
            $error = new InternalException($e->getMessage(), $e->getCode(), $e);
151
            $error->throwsIn(File::fromPathname($e->getFile()), $e->getLine(), 0);
0 ignored issues
show
Bug introduced by
It seems like \Railt\Io\File::fromPathname($e->getFile()) targeting Railt\Io\File::fromPathname() can also be of type object<Railt\Io\File>; however, Railt\Io\Exception\Exter...leException::throwsIn() does only seem to accept object<Railt\Io\Readable>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
152
153
            throw $error;
154
        }
155
    }
156
}
157