akoSalman /
Shorturl
| 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
}
}
Loading history...
|
|||
| 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
}
}
Loading history...
|
|||
| 169 | } |
||
| 170 | } |
||
| 171 | } |
Checks if undeclared accessed properties appear in database migrations and if the creating migration is correct.