1 | <?php |
||
2 | namespace Ako\Shorturl\Drivers; |
||
3 | |||
4 | use Illuminate\Support\Str; |
||
5 | use Ako\Shorturl\Models\Link; |
||
6 | |||
7 | class LocalDriver implements BaseDriver |
||
8 | { |
||
9 | protected $props = []; |
||
10 | protected $config, $main_str, $head, $tail, $base_url, $path; |
||
11 | |||
12 | public function __construct () |
||
13 | { |
||
14 | $this->config = config('shorturl.drivers.local'); |
||
15 | $this->main_str = $this->config['str_shuffled']; |
||
16 | $this->head = $this->main_str[0]; |
||
17 | $this->tail = $this->main_str[strlen($this->main_str) - 1]; |
||
18 | $this->checkCaseSensitive (); |
||
19 | } |
||
20 | |||
21 | /** |
||
22 | * @param string $url |
||
23 | * |
||
24 | * @return string |
||
25 | */ |
||
26 | public function expand (string $url) :string |
||
27 | { |
||
28 | $this->parseUrl($url); |
||
29 | $link = Link::where("short_path", $this->path)->first(); |
||
30 | if ($link) { |
||
31 | $link->increment("clicks"); |
||
32 | return $link->base_url . "/" . $link->long_path; |
||
0 ignored issues
–
show
|
|||
33 | } |
||
34 | return ""; |
||
35 | } |
||
36 | |||
37 | /** |
||
38 | * @param string $url |
||
39 | * |
||
40 | * @return string |
||
41 | * @throws \Exception |
||
42 | */ |
||
43 | public function shorten (string $url) :string |
||
44 | { |
||
45 | $this->parseUrl ($url); |
||
46 | |||
47 | // Check if given url has been shorten previously |
||
48 | $duplicate = Link::where(['long_path' => $this->path])->first(); |
||
49 | if ($duplicate) |
||
50 | return $duplicate->base_url . "/" . $duplicate->short_path; |
||
0 ignored issues
–
show
|
|||
51 | |||
52 | $short_path = $this->getNextShortpath(); |
||
53 | |||
54 | try { |
||
55 | Link::create([ |
||
56 | "long_path" => $this->path, |
||
57 | "short_path" => $short_path, |
||
58 | 'base_url' => $this->base_url, |
||
59 | 'properties' => $this->props] |
||
60 | ); |
||
61 | } catch (\Exception $e) { |
||
62 | // If it is duplicate entry exception |
||
63 | // try to insert a new entry |
||
64 | if ($e instanceof \Illuminate\Database\QueryException && $e->getCode() == "23000") { |
||
65 | return $this->shorten($url); |
||
66 | } |
||
67 | } |
||
68 | return $this->base_url . "/" . $short_path; |
||
69 | } |
||
70 | |||
71 | /** |
||
72 | * Git the first short url |
||
73 | * |
||
74 | * @return string |
||
75 | */ |
||
76 | private function getFirstUrl () : string |
||
77 | { |
||
78 | $min_length = $this->config['min_length']; |
||
79 | $short_path = ""; |
||
80 | for ($i = 0; $i < $min_length; $i++) |
||
81 | $short_path .= $this->head; |
||
82 | return $short_path; |
||
83 | } |
||
84 | |||
85 | /** |
||
86 | * |
||
87 | * Get the next short url based on the given item (it gets permutations one by one) |
||
88 | * |
||
89 | * @param string $current_perm |
||
90 | * @return string |
||
91 | */ |
||
92 | private function findNextPerm (string $current_perm) :string |
||
93 | { |
||
94 | if (!strlen($current_perm)) |
||
95 | return $this->head; |
||
96 | |||
97 | $arr = array_reverse(str_split($current_perm)); |
||
98 | foreach($arr as $key => $current_char) { |
||
99 | if ($current_char == $this->tail) { |
||
100 | $current_perm = Str::replaceLast($current_char, "", $current_perm); |
||
101 | return $this->findNextPerm($current_perm) . $this->head; |
||
102 | } |
||
103 | $next_char = str_split(Str::after($this->main_str, $current_char))[0]; |
||
104 | return Str::replaceLast($current_char, $next_char, $current_perm); |
||
105 | } |
||
0 ignored issues
–
show
In this branch, the function will implicitly return
null which is incompatible with the type-hinted return string . Consider adding a return statement or allowing null as return value.
For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example: interface ReturnsInt {
public function returnsIntHinted(): int;
}
class MyClass implements ReturnsInt {
public function returnsIntHinted(): int
{
if (foo()) {
return 123;
}
// here: null is implicitly returned
}
}
![]() |
|||
106 | } |
||
107 | |||
108 | /** |
||
109 | * @param array $props |
||
110 | * |
||
111 | * @return LocalDriver |
||
112 | */ |
||
113 | public function withProperties (array $props = []) :LocalDriver |
||
114 | { |
||
115 | $this->props = array_merge($this->props, $props); |
||
116 | return $this; |
||
117 | } |
||
118 | |||
119 | private function parseUrl (string $url) |
||
120 | { |
||
121 | $parse = parse_url($url); |
||
122 | $path = ""; |
||
123 | if ($parse['path'] ?? null) |
||
124 | $path .= str::replaceFirst("/", "", $parse['path']); |
||
125 | if ($parse['query'] ?? null) |
||
126 | $path .= "?" . $parse['query']; |
||
127 | if ($parse['fragment'] ?? null) |
||
128 | $path .= "#" . $parse['fragment']; |
||
129 | |||
130 | $this->base_url = str_replace("/" . $path, "", $url); |
||
131 | $this->path = $path; |
||
132 | } |
||
133 | |||
134 | private function checkCaseSensitive () |
||
135 | { |
||
136 | if ((!$this->config['case_sensitive'] ?? null)) { |
||
137 | // Remove upper cases from main string |
||
138 | $this->main_str = preg_replace("/(.)\\1+/", "$1", strtolower($this->main_str)); |
||
139 | } |
||
140 | } |
||
141 | |||
142 | /** |
||
143 | * @return string |
||
144 | */ |
||
145 | private function getNextShortpath () : string |
||
146 | { |
||
147 | $tbl = (new Link)->getTable(); |
||
148 | // Get latest short_path(s) |
||
149 | // As multiple instances could be created at the same timestamp which we get |
||
150 | // the latest one based on that |
||
151 | // so we must find the latest one(short_path) in the permutation of the main_str |
||
152 | $latest = collect(\DB::select("SELECT short_path FROM $tbl WHERE created_at = (SELECT MAX(created_at) FROM $tbl)")); |
||
153 | |||
154 | if (!$latest->count()) |
||
155 | return $this->getFirstUrl(); |
||
156 | |||
157 | if ($latest->count() == 1) { |
||
158 | return $this->findNextPerm($latest->first()->short_path); |
||
159 | } |
||
160 | else { |
||
161 | foreach ($latest->reverse() as $key => $item) { |
||
162 | $next = $this->findNextPerm($item->short_path); |
||
163 | |||
164 | // If next permutation of current item is in fetched items |
||
165 | // find next permutation of the next fetched one |
||
166 | if (!$latest->contains("short_path", $next)) |
||
167 | return $next; |
||
168 | } |
||
0 ignored issues
–
show
In this branch, the function will implicitly return
null which is incompatible with the type-hinted return string . Consider adding a return statement or allowing null as return value.
For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example: interface ReturnsInt {
public function returnsIntHinted(): int;
}
class MyClass implements ReturnsInt {
public function returnsIntHinted(): int
{
if (foo()) {
return 123;
}
// here: null is implicitly returned
}
}
![]() |
|||
169 | } |
||
170 | } |
||
171 | } |
Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.