diff --git a/.github/workflows/analyse.yaml b/.github/workflows/analyse.yaml
new file mode 100644
index 00000000..e0ae6755
--- /dev/null
+++ b/.github/workflows/analyse.yaml
@@ -0,0 +1,36 @@
+name: Static analysis
+
+on:
+  push:
+  pull_request:
+
+jobs:
+  phpstan:
+    name: PHPStan
+    runs-on: ubuntu-20.04
+    
+    # 'push' runs on inner branches, 'pull_request' will run only on outer PRs
+    if: >
+      github.event_name == 'push'
+      || (github.event_name == 'pull_request'
+          && github.event.pull_request.head.repo.full_name != github.repository)
+
+    steps:
+      - name: Code Checkout
+        uses: actions/checkout@v4
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+
+      - name: Build and start Docker container
+        working-directory: install/automated/docker
+        run: |
+          docker build -t openvk ../../.. -f openvk.Dockerfile
+
+      - name: Run Docker container with PHPStan
+        working-directory: install/automated/docker
+        run: |
+          docker container run --rm \
+            -v ./chandler.example.yml:/opt/chandler/chandler.yml \
+            -v ./openvk.example.yml:/opt/chandler/extensions/available/openvk/openvk.yml \
+            openvk vendor/bin/phpstan analyse --memory-limit 1G
diff --git a/CLI/FetchToncoinTransactions.php b/CLI/FetchToncoinTransactions.php
index 9de2c998..8342197f 100755
--- a/CLI/FetchToncoinTransactions.php
+++ b/CLI/FetchToncoinTransactions.php
@@ -18,6 +18,7 @@ define("NANOTON", 1000000000);
 class FetchToncoinTransactions extends Command
 {
     private $images;
+    private $transactions;
 
     protected static $defaultName = "fetch-ton";
 
diff --git a/ServiceAPI/Wall.php b/ServiceAPI/Wall.php
index 4066d4f3..db6c32b6 100644
--- a/ServiceAPI/Wall.php
+++ b/ServiceAPI/Wall.php
@@ -13,6 +13,7 @@ class Wall implements Handler
     protected $user;
     protected $posts;
     protected $notes;
+    protected $videos;
 
     public function __construct(?User $user)
     {
diff --git a/VKAPI/Handlers/Board.php b/VKAPI/Handlers/Board.php
index f2f3ec76..28328586 100644
--- a/VKAPI/Handlers/Board.php
+++ b/VKAPI/Handlers/Board.php
@@ -248,8 +248,9 @@ final class Board extends VKAPIRequestHandler
         return 1;
     }
 
-    public function editComment(int $comment_id, int $group_id = 0, int $topic_id = 0, string $message, string $attachments)
+    public function editComment(string $message, string $attachments, int $comment_id, int $group_id = 0, int $topic_id = 0)
     {
+        # FIXME
         /*
         $this->requireUser();
         $this->willExecuteWriteAction();
diff --git a/VKAPI/Handlers/Groups.php b/VKAPI/Handlers/Groups.php
index baf78210..7f80d108 100644
--- a/VKAPI/Handlers/Groups.php
+++ b/VKAPI/Handlers/Groups.php
@@ -45,7 +45,7 @@ final class Groups extends VKAPIRequestHandler
             $clbsCount = $user->getClubCount();
         }
 
-        $rClubs;
+        $rClubs = [];
 
         $ic = sizeof($clbs);
         if (sizeof($clbs) > $count) {
diff --git a/VKAPI/Handlers/Newsfeed.php b/VKAPI/Handlers/Newsfeed.php
index 346d5312..5700ada7 100644
--- a/VKAPI/Handlers/Newsfeed.php
+++ b/VKAPI/Handlers/Newsfeed.php
@@ -52,7 +52,7 @@ final class Newsfeed extends VKAPIRequestHandler
         return $response;
     }
 
-    public function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0, int $rss = 0)
+    public function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0, int $rss = 0, int $return_banned = 0)
     {
         $this->requireUser();
 
diff --git a/VKAPI/Handlers/Notes.php b/VKAPI/Handlers/Notes.php
index cdf5aa45..bde9df75 100644
--- a/VKAPI/Handlers/Notes.php
+++ b/VKAPI/Handlers/Notes.php
@@ -185,12 +185,14 @@ final class Notes extends VKAPIRequestHandler
             $this->fail(15, "Access denied");
         }
 
+        $nodez = (object) [
+            "count" => 0,
+            "notes" => [],
+        ];
         if (empty($note_ids)) {
+            $nodez->count = (new NotesRepo())->getUserNotesCount($user);
+
             $notes = array_slice(iterator_to_array((new NotesRepo())->getUserNotes($user, 1, $count + $offset, $sort == 0 ? "ASC" : "DESC")), $offset);
-            $nodez = (object) [
-                "count" => (new NotesRepo())->getUserNotesCount((new UsersRepo())->get($user_id)),
-                "notes" => [],
-            ];
 
             foreach ($notes as $note) {
                 if ($note->isDeleted()) {
@@ -210,6 +212,7 @@ final class Notes extends VKAPIRequestHandler
                 $note = (new NotesRepo())->getNoteById((int) $id[0], (int) $id[1]);
                 if ($note && !$note->isDeleted()) {
                     $nodez->notes[] = $note->toVkApiStruct();
+                    $nodez->count++;
                 }
             }
         }
diff --git a/VKAPI/Handlers/Photos.php b/VKAPI/Handlers/Photos.php
index 1863daaf..d13d3fc3 100644
--- a/VKAPI/Handlers/Photos.php
+++ b/VKAPI/Handlers/Photos.php
@@ -9,6 +9,7 @@ use Nette\Utils\ImageException;
 use openvk\Web\Models\Entities\{Photo, Album, Comment};
 use openvk\Web\Models\Repositories\Albums;
 use openvk\Web\Models\Repositories\Photos as PhotosRepo;
+use openvk\Web\Models\Repositories\Videos as VideosRepo;
 use openvk\Web\Models\Repositories\Clubs;
 use openvk\Web\Models\Repositories\Users as UsersRepo;
 use openvk\Web\Models\Repositories\Comments as CommentsRepo;
diff --git a/VKAPI/Handlers/Users.php b/VKAPI/Handlers/Users.php
index e3d27073..bafce3d8 100644
--- a/VKAPI/Handlers/Users.php
+++ b/VKAPI/Handlers/Users.php
@@ -292,14 +292,14 @@ final class Users extends VKAPIRequestHandler
                                 break;
                             case 'blacklisted_by_me':
                                 if (!$authuser) {
-                                    continue;
+                                    break;
                                 }
 
                                 $response[$i]->blacklisted_by_me = (int) $usr->isBlacklistedBy($this->getUser());
                                 break;
                             case 'blacklisted':
                                 if (!$authuser) {
-                                    continue;
+                                    break;
                                 }
 
                                 $response[$i]->blacklisted = (int) $this->getUser()->isBlacklistedBy($usr);
@@ -383,7 +383,8 @@ final class Users extends VKAPIRequestHandler
         string $fav_music = "",
         string $fav_films = "",
         string $fav_shows = "",
-        string $fav_books = ""
+        string $fav_books = "",
+        string $interests = ""
     ) {
         if ($count > 100) {
             $this->fail(100, "One of the parameters specified was missing or invalid: count should be less or equal to 100");
diff --git a/VKAPI/Handlers/VKAPIRequestHandler.php b/VKAPI/Handlers/VKAPIRequestHandler.php
index b67c4797..4804a8dd 100644
--- a/VKAPI/Handlers/VKAPIRequestHandler.php
+++ b/VKAPI/Handlers/VKAPIRequestHandler.php
@@ -20,7 +20,7 @@ abstract class VKAPIRequestHandler
         $this->platform = $platform;
     }
 
-    protected function fail(int $code, string $message): void
+    protected function fail(int $code, string $message): never
     {
         throw new APIErrorException($message, $code);
     }
diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php
index 3bc56b93..a906596b 100644
--- a/VKAPI/Handlers/Wall.php
+++ b/VKAPI/Handlers/Wall.php
@@ -53,7 +53,7 @@ final class Wall extends VKAPIRequestHandler
             $this->fail(15, "Access denied: wall is disabled");
         } // Don't search for logic here pls
 
-        $iteratorv;
+        $iteratorv = null;
 
         switch ($filter) {
             case "all":
@@ -722,7 +722,7 @@ final class Wall extends VKAPIRequestHandler
             $post->attach($attachment);
         }
 
-        if ($wall > 0 && $wall !== $this->user->identity->getId()) {
+        if ($owner_id > 0 && $owner_id !== $this->user->identity->getId()) {
             (new WallPostNotification($wallOwner, $post, $this->user->identity))->emit();
         }
 
@@ -734,7 +734,7 @@ final class Wall extends VKAPIRequestHandler
         $this->requireUser();
         $this->willExecuteWriteAction();
 
-        $postArray;
+        $postArray = [];
         if (preg_match('/(wall|video|photo)((?:-?)[0-9]+)_([0-9]+)/', $object, $postArray) == 0) {
             $this->fail(100, "One of the parameters specified was missing or invalid: object is incorrect");
         }
diff --git a/Web/Models/Entities/Document.php b/Web/Models/Entities/Document.php
index 286f9c0b..a264ad71 100644
--- a/Web/Models/Entities/Document.php
+++ b/Web/Models/Entities/Document.php
@@ -351,7 +351,7 @@ class Document extends Media
         return $this->getRecord()->owner;
     }
 
-    public function toApiPreview(): object
+    public function toApiPreview(): ?object
     {
         $preview = $this->getPreview();
         if ($preview instanceof Photo) {
@@ -360,6 +360,8 @@ class Document extends Media
                     "sizes" => array_values($preview->getVkApiSizes()),
                 ],
             ];
+        } else {
+            return null;
         }
     }
 
diff --git a/Web/Models/Entities/Gift.php b/Web/Models/Entities/Gift.php
index f63b7b81..f4a76b35 100644
--- a/Web/Models/Entities/Gift.php
+++ b/Web/Models/Entities/Gift.php
@@ -112,7 +112,6 @@ class Gift extends RowModel
 
     public function setImage(string $file): bool
     {
-        $imgBlob;
         try {
             $image = Image::fromFile($file);
             $image->resize(512, 512, Image::SHRINK_ONLY);
diff --git a/Web/Models/Entities/Message.php b/Web/Models/Entities/Message.php
index 4e9b8455..1d3364c5 100644
--- a/Web/Models/Entities/Message.php
+++ b/Web/Models/Entities/Message.php
@@ -33,6 +33,8 @@ class Message extends RowModel
             return (new Users())->get($this->getRecord()->sender_id);
         } elseif ($this->getRecord()->sender_type === 'openvk\Web\Models\Entities\Club') {
             return (new Clubs())->get($this->getRecord()->sender_id);
+        } else {
+            return null;
         }
     }
 
@@ -49,6 +51,8 @@ class Message extends RowModel
             return (new Users())->get($this->getRecord()->recipient_id);
         } elseif ($this->getRecord()->recipient_type === 'openvk\Web\Models\Entities\Club') {
             return (new Clubs())->get($this->getRecord()->recipient_id);
+        } else {
+            return null;
         }
     }
 
@@ -147,7 +151,7 @@ class Message extends RowModel
                 "id"     => $author->getId(),
                 "link"   => $_SERVER['REQUEST_SCHEME'] . "://" . $_SERVER['HTTP_HOST'] . $author->getURL(),
                 "avatar" => $author->getAvatarUrl(),
-                "name"   => $author->getFirstName() . $unreadmsg,
+                "name"   => $author->getFirstName(),
             ],
             "timing" => [
                 "sent"   => (string) $this->getSendTimeHumanized(),
diff --git a/Web/Models/Entities/Notifications/Notification.php b/Web/Models/Entities/Notifications/Notification.php
index 67d36497..6a6866c1 100644
--- a/Web/Models/Entities/Notifications/Notification.php
+++ b/Web/Models/Entities/Notifications/Notification.php
@@ -64,13 +64,15 @@ class Notification
         return $this->recipient;
     }
 
-    public function getModel(int $index): RowModel
+    public function getModel(int $index): ?RowModel
     {
         switch ($index) {
             case 0:
                 return $this->originModel;
             case 1:
                 return $this->targetModel;
+            default:
+                return null;
         }
     }
 
diff --git a/Web/Models/Entities/Photo.php b/Web/Models/Entities/Photo.php
index 1eb42110..470a0a8a 100644
--- a/Web/Models/Entities/Photo.php
+++ b/Web/Models/Entities/Photo.php
@@ -385,9 +385,9 @@ class Photo extends Media
         }
     }
 
-    public static function fastMake(int $owner, string $description = "", array $file, ?Album $album = null, bool $anon = false): Photo
+    public static function fastMake(int $owner, string $description, array $file, ?Album $album = null, bool $anon = false): Photo
     {
-        $photo = new static();
+        $photo = new Photo();
         $photo->setOwner($owner);
         $photo->setDescription(iconv_substr($description, 0, 36) . "...");
         $photo->setAnonymous($anon);
diff --git a/Web/Models/Entities/Report.php b/Web/Models/Entities/Report.php
index db8d38e6..1d345fd4 100644
--- a/Web/Models/Entities/Report.php
+++ b/Web/Models/Entities/Report.php
@@ -45,11 +45,7 @@ class Report extends RowModel
 
     public function isDeleted(): bool
     {
-        if ($this->getRecord()->deleted === 0) {
-            return false;
-        } elseif ($this->getRecord()->deleted === 1) {
-            return true;
-        }
+        return $this->getRecord()->deleted === 1;
     }
 
     public function authorId(): int
diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php
index 85731811..cb85775c 100644
--- a/Web/Models/Entities/User.php
+++ b/Web/Models/Entities/User.php
@@ -933,6 +933,7 @@ class User extends RowModel
             case 1:
                 return tr('female');
             case 2:
+            default:
                 return tr('neutral');
         }
     }
@@ -1561,14 +1562,14 @@ class User extends RowModel
                     break;
                 case "blacklisted_by_me":
                     if (!$user) {
-                        continue;
+                        break;
                     }
 
                     $res->blacklisted_by_me = (int) $this->isBlacklistedBy($user);
                     break;
                 case "blacklisted":
                     if (!$user) {
-                        continue;
+                        break;
                     }
 
                     $res->blacklisted = (int) $user->isBlacklistedBy($this);
diff --git a/Web/Models/Entities/Video.php b/Web/Models/Entities/Video.php
index b8a9fcca..8dbfb7a1 100644
--- a/Web/Models/Entities/Video.php
+++ b/Web/Models/Entities/Video.php
@@ -9,12 +9,13 @@ use openvk\Web\Util\Shell\Exceptions\{ShellUnavailableException, UnknownCommandE
 use openvk\Web\Models\VideoDrivers\VideoDriver;
 use Nette\InvalidStateException as ISE;
 
-define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<", false);
+define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<");
 
 class Video extends Media
 {
-    public const TYPE_DIRECT = 0;
-    public const TYPE_EMBED  = 1;
+    public const TYPE_DIRECT  = 0;
+    public const TYPE_EMBED   = 1;
+    public const TYPE_UNKNOWN = -1;
 
     protected $tableName     = "videos";
     protected $fileExtension = "mp4";
@@ -108,6 +109,7 @@ class Video extends Media
         } elseif (!is_null($this->getRecord()->link)) {
             return Video::TYPE_EMBED;
         }
+        return Video::TYPE_UNKNOWN;
     }
 
     public function getVideoDriver(): ?VideoDriver
@@ -238,7 +240,7 @@ class Video extends Media
         $this->save();
     }
 
-    public static function fastMake(int $owner, string $name = "Unnamed Video.ogv", string $description = "", array $file, bool $unlisted = true, bool $anon = false): Video
+    public static function fastMake(int $owner, string $name, string $description, array $file, bool $unlisted = true, bool $anon = false): Video
     {
         if (OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']) {
             exit(VIDEOS_FRIENDLY_ERROR);
@@ -269,7 +271,7 @@ class Video extends Media
             return false;
         }
 
-        $streams   = Shell::ffprobe("-i", $path, "-show_streams", "-select_streams v", "-loglevel error")->execute($error);
+        $streams   = Shell::ffprobe("-i", $path, "-show_streams", "-select_streams v", "-loglevel error")->execute();
         $durations = [];
         preg_match_all('%duration=([0-9\.]++)%', $streams, $durations);
 
diff --git a/Web/Models/Repositories/ChandlerGroups.php b/Web/Models/Repositories/ChandlerGroups.php
index 3c7c62de..ad43da2e 100644
--- a/Web/Models/Repositories/ChandlerGroups.php
+++ b/Web/Models/Repositories/ChandlerGroups.php
@@ -13,6 +13,8 @@ class ChandlerGroups
 {
     private $context;
     private $groups;
+    private $members;
+    private $perms;
 
     public function __construct()
     {
diff --git a/Web/Models/Repositories/Clubs.php b/Web/Models/Repositories/Clubs.php
index b425c858..fcc9d5f8 100644
--- a/Web/Models/Repositories/Clubs.php
+++ b/Web/Models/Repositories/Clubs.php
@@ -91,7 +91,7 @@ class Clubs
         return (clone $this->clubs)->count('*');
     }
 
-    public function getPopularClubs(): \Traversable
+    public function getPopularClubs(): ?\Traversable
     {
         // TODO rewrite
 
@@ -106,6 +106,8 @@ class Clubs
                 "subscriptions" => $entry["subscriptions"],
             ];
         */
+        trigger_error("Clubs::getPopularClubs() is currently commented out and returns null", E_USER_WARNING);
+        return null;
     }
 
     public function getWriteableClubs(int $id): \Traversable
diff --git a/Web/Models/Repositories/Conversations.php b/Web/Models/Repositories/Conversations.php
deleted file mode 100644
index e354411b..00000000
--- a/Web/Models/Repositories/Conversations.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace openvk\Web\Models\Repositories;
-
-use openvk\Web\Models\Entities\{Messages as M, User};
-use Chandler\Database\DatabaseConnection as DB;
-use Nette\Database\Table\ActiveRow;
-
-class Conversations
-{
-    private $context;
-    private $convos;
-
-    public function __construct()
-    {
-        $this->context = DB::i()->getContext();
-        $this->convos  = $this->context->table("conversations");
-    }
-
-    private function toConversation(?ActiveRow $ar): ?M\AbstractConversation
-    {
-        if (is_null($ar)) {
-            return null;
-        } elseif ($ar->is_pm) {
-            return new M\PrivateConversation($ar);
-        } else {
-            return new M\Conversation($ar);
-        }
-    }
-
-    public function get(int $id): ?M\AbstractConversation
-    {
-        return $this->toConversation($this->convos->get($id));
-    }
-
-    public function getConversationsByUser(User $user, int $page = 1, ?int $perPage = null): \Traversable
-    {
-        $rels = $this->context->table("conversation_members")->where([
-            "deleted"   => false,
-            "user"      => $user->getId(),
-        ])->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE);
-        foreach ($rels as $rel) {
-            yield $this->get($rel->conversation);
-        }
-    }
-
-    public function getPrivateConversation(User $user, int $peer): M\PrivateConversation
-    {
-        ;
-    }
-}
diff --git a/Web/Models/Repositories/Documents.php b/Web/Models/Repositories/Documents.php
index cda95abc..db98fc5d 100644
--- a/Web/Models/Repositories/Documents.php
+++ b/Web/Models/Repositories/Documents.php
@@ -152,7 +152,7 @@ class Documents
             switch ($paramName) {
                 case "type":
                     if ($paramValue < 1 || $paramValue > 8) {
-                        continue;
+                        break;
                     }
                     $result->where("type", $paramValue);
                     break;
diff --git a/Web/Models/Repositories/SupportAgents.php b/Web/Models/Repositories/SupportAgents.php
index b0dfcbbc..d83d1974 100644
--- a/Web/Models/Repositories/SupportAgents.php
+++ b/Web/Models/Repositories/SupportAgents.php
@@ -11,7 +11,7 @@ use openvk\Web\Models\Entities\{User, SupportAgent};
 class SupportAgents
 {
     private $context;
-    private $tickets;
+    private $agents;
 
     public function __construct()
     {
diff --git a/Web/Models/Repositories/Tickets.php b/Web/Models/Repositories/Tickets.php
index dd7379c8..6728931e 100644
--- a/Web/Models/Repositories/Tickets.php
+++ b/Web/Models/Repositories/Tickets.php
@@ -57,7 +57,7 @@ class Tickets
     {
         $requests = $this->tickets->where(["id" => $requestId])->fetch();
         if (!is_null($requests)) {
-            return new Req($requests);
+            return new Ticket($requests);
         } else {
             return null;
         }
diff --git a/Web/Presenters/AdminPresenter.php b/Web/Presenters/AdminPresenter.php
index f0f2b04d..8fad1052 100644
--- a/Web/Presenters/AdminPresenter.php
+++ b/Web/Presenters/AdminPresenter.php
@@ -255,7 +255,7 @@ final class AdminPresenter extends OpenVKPresenter
     {
         $this->warnIfNoCommerce();
 
-        $cat;
+        $cat = null;
         $gen = false;
         if ($id !== 0) {
             $cat = $this->gifts->getCat($id);
diff --git a/Web/Presenters/BlobPresenter.php b/Web/Presenters/BlobPresenter.php
index 99b57816..619d9d54 100644
--- a/Web/Presenters/BlobPresenter.php
+++ b/Web/Presenters/BlobPresenter.php
@@ -34,7 +34,8 @@ final class BlobPresenter extends OpenVKPresenter
         }
 
         if (isset($_SERVER["HTTP_IF_NONE_MATCH"])) {
-            exit(header("HTTP/1.1 304 Not Modified"));
+            header("HTTP/1.1 304 Not Modified");
+            exit();
         }
 
         header("Content-Type: " . mime_content_type($path));
diff --git a/Web/Presenters/CommentPresenter.php b/Web/Presenters/CommentPresenter.php
index 0353acf8..f9317cff 100644
--- a/Web/Presenters/CommentPresenter.php
+++ b/Web/Presenters/CommentPresenter.php
@@ -7,6 +7,7 @@ namespace openvk\Web\Presenters;
 use openvk\Web\Models\Entities\{Comment, Notifications\MentionNotification, Photo, Video, User, Topic, Post};
 use openvk\Web\Models\Entities\Notifications\CommentNotification;
 use openvk\Web\Models\Repositories\{Comments, Clubs, Videos, Photos, Audios};
+use Nette\InvalidStateException as ISE;
 
 final class CommentPresenter extends OpenVKPresenter
 {
diff --git a/Web/Presenters/ContentSearchPresenter.php b/Web/Presenters/ContentSearchPresenter.php
index 6fadd687..e22f34fa 100644
--- a/Web/Presenters/ContentSearchPresenter.php
+++ b/Web/Presenters/ContentSearchPresenter.php
@@ -8,17 +8,17 @@ use openvk\Web\Models\Repositories\ContentSearchRepository;
 
 final class ContentSearchPresenter extends OpenVKPresenter
 {
-    private $repo;
+    protected $repo;
 
-    public function __construct(ContentSearchRepository $repo)
+    public function __construct(ContentSearchRepository $repository)
     {
-        $this->repo = $repo;
+        $this->repo = $repository;
     }
 
     public function renderIndex(): void
     {
         if ($_SERVER["REQUEST_METHOD"] === "POST") {
-            $this->template->results = $repo->find([
+            $this->template->results = $this->repo->find([
                 "query" => $this->postParam("query"),
             ]);
         }
diff --git a/Web/Presenters/GroupPresenter.php b/Web/Presenters/GroupPresenter.php
index b3e27512..ba9ec1f4 100644
--- a/Web/Presenters/GroupPresenter.php
+++ b/Web/Presenters/GroupPresenter.php
@@ -9,6 +9,7 @@ use Nette\InvalidStateException;
 use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification;
 use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics, Audios, Posts, Documents};
 use Chandler\Security\Authenticator;
+use Nette\InvalidStateException as ISE;
 
 final class GroupPresenter extends OpenVKPresenter
 {
@@ -288,7 +289,6 @@ final class GroupPresenter extends OpenVKPresenter
 
                     (new Albums())->getClubAvatarAlbum($club)->addPhoto($photo);
                 } catch (ISE $ex) {
-                    $name = $album->getName();
                     $this->flashFail("err", tr("error"), tr("error_when_uploading_photo"));
                 }
             }
diff --git a/Web/Presenters/MessengerPresenter.php b/Web/Presenters/MessengerPresenter.php
index 70054620..5c8c6a6e 100644
--- a/Web/Presenters/MessengerPresenter.php
+++ b/Web/Presenters/MessengerPresenter.php
@@ -93,9 +93,11 @@ final class MessengerPresenter extends OpenVKPresenter
         header("Content-Type: application/json");
 
         if ($this->queryParam("act") !== "a_check") {
-            exit(header("HTTP/1.1 400 Bad Request"));
+            header("HTTP/1.1 400 Bad Request");
+            exit();
         } elseif (!$this->queryParam("key")) {
-            exit(header("HTTP/1.1 403 Forbidden"));
+            header("HTTP/1.1 403 Forbidden");
+            exit();
         }
 
         $key       = $this->queryParam("key");
@@ -158,7 +160,8 @@ final class MessengerPresenter extends OpenVKPresenter
 
         $sel = $this->getCorrespondent($sel);
         if ($sel->getId() !== $this->user->id && !$sel->getPrivacyPermission('messages.write', $this->user->identity)) {
-            exit(header("HTTP/1.1 403 Forbidden"));
+            header("HTTP/1.1 403 Forbidden");
+            exit();
         }
 
         $cor = new Correspondence($this->user->identity, $sel);
diff --git a/Web/Presenters/NoSpamPresenter.php b/Web/Presenters/NoSpamPresenter.php
index 4f49d3b1..e6da910b 100644
--- a/Web/Presenters/NoSpamPresenter.php
+++ b/Web/Presenters/NoSpamPresenter.php
@@ -151,77 +151,6 @@ final class NoSpamPresenter extends OpenVKPresenter
         $this->assertNoCSRF();
         $this->willExecuteWriteAction();
 
-        function searchByAdditionalParams(?string $table = null, ?string $where = null, ?string $ip = null, ?string $useragent = null, ?int $ts = null, ?int $te = null, $user = null)
-        {
-            $db = DatabaseConnection::i()->getContext();
-            if ($table && ($ip || $useragent || $ts || $te || $user)) {
-                $conditions = [];
-
-                if ($ip) {
-                    $conditions[] = "`ip` REGEXP '$ip'";
-                }
-                if ($useragent) {
-                    $conditions[] = "`useragent` REGEXP '$useragent'";
-                }
-                if ($ts) {
-                    $conditions[] = "`ts` < $ts";
-                }
-                if ($te) {
-                    $conditions[] = "`ts` > $te";
-                }
-                if ($user) {
-                    $users = new Users();
-
-                    $_user = $users->getByChandlerUser((new ChandlerUsers())->getById($user))
-                        ?? $users->get((int) $user)
-                        ?? $users->getByAddress($user)
-                        ?? null;
-
-                    if ($_user) {
-                        $conditions[] = "`user` = '" . $_user->getChandlerGUID() . "'";
-                    }
-                }
-
-                $whereStart = "WHERE `object_table` = '$table'";
-                if ($table === "profiles") {
-                    $whereStart .= "AND `type` = 0";
-                }
-
-                $conditions = count($conditions) > 0 ? "AND (" . implode(" AND ", $conditions) . ")" : "";
-                $response = [];
-
-                if ($conditions) {
-                    $logs = $db->query("SELECT * FROM `ChandlerLogs` $whereStart $conditions GROUP BY `object_id`, `object_model`");
-
-                    foreach ($logs as $log) {
-                        $log = (new Logs())->get($log->id);
-                        $object = $log->getObject()->unwrap();
-
-                        if (!$object) {
-                            continue;
-                        }
-                        if ($where) {
-                            if (str_starts_with($where, " AND")) {
-                                $where = substr_replace($where, "", 0, strlen(" AND"));
-                            }
-
-                            $a = $db->query("SELECT * FROM `$table` WHERE $where")->fetchAll();
-                            foreach ($a as $o) {
-                                if ($object->id == $o["id"]) {
-                                    $response[] = $object;
-                                }
-                            }
-
-                        } else {
-                            $response[] = $object;
-                        }
-                    }
-                }
-
-                return $response;
-            }
-        }
-
         try {
             $response = [];
             $processed = 0;
@@ -290,7 +219,7 @@ final class NoSpamPresenter extends OpenVKPresenter
                 }
 
                 if ($ip || $useragent || $ts || $te || $user) {
-                    $rows = searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user);
+                    $rows = $this->searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user);
                 } else {
                     if (!$where) {
                         $rows = [];
@@ -408,4 +337,75 @@ final class NoSpamPresenter extends OpenVKPresenter
             $this->returnJson(["success" => false, "error" => $e->getMessage()]);
         }
     }
+
+    private function searchByAdditionalParams(?string $table = null, ?string $where = null, ?string $ip = null, ?string $useragent = null, ?int $ts = null, ?int $te = null, $user = null)
+    {
+        $db = DatabaseConnection::i()->getContext();
+        if ($table && ($ip || $useragent || $ts || $te || $user)) {
+            $conditions = [];
+
+            if ($ip) {
+                $conditions[] = "`ip` REGEXP '$ip'";
+            }
+            if ($useragent) {
+                $conditions[] = "`useragent` REGEXP '$useragent'";
+            }
+            if ($ts) {
+                $conditions[] = "`ts` < $ts";
+            }
+            if ($te) {
+                $conditions[] = "`ts` > $te";
+            }
+            if ($user) {
+                $users = new Users();
+
+                $_user = $users->getByChandlerUser((new ChandlerUsers())->getById($user))
+                    ?? $users->get((int) $user)
+                    ?? $users->getByAddress($user)
+                    ?? null;
+
+                if ($_user) {
+                    $conditions[] = "`user` = '" . $_user->getChandlerGUID() . "'";
+                }
+            }
+
+            $whereStart = "WHERE `object_table` = '$table'";
+            if ($table === "profiles") {
+                $whereStart .= "AND `type` = 0";
+            }
+
+            $conditions = count($conditions) > 0 ? "AND (" . implode(" AND ", $conditions) . ")" : "";
+            $response = [];
+
+            if ($conditions) {
+                $logs = $db->query("SELECT * FROM `ChandlerLogs` $whereStart $conditions GROUP BY `object_id`, `object_model`");
+
+                foreach ($logs as $log) {
+                    $log = (new Logs())->get($log->id);
+                    $object = $log->getObject()->unwrap();
+
+                    if (!$object) {
+                        continue;
+                    }
+                    if ($where) {
+                        if (str_starts_with($where, " AND")) {
+                            $where = substr_replace($where, "", 0, strlen(" AND"));
+                        }
+
+                        $a = $db->query("SELECT * FROM `$table` WHERE $where")->fetchAll();
+                        foreach ($a as $o) {
+                            if ($object->id == $o["id"]) {
+                                $response[] = $object;
+                            }
+                        }
+
+                    } else {
+                        $response[] = $object;
+                    }
+                }
+            }
+
+            return $response;
+        }
+    }
 }
diff --git a/Web/Presenters/OpenVKPresenter.php b/Web/Presenters/OpenVKPresenter.php
index 122e0966..478670f0 100644
--- a/Web/Presenters/OpenVKPresenter.php
+++ b/Web/Presenters/OpenVKPresenter.php
@@ -9,6 +9,7 @@ use Chandler\MVC\SimplePresenter;
 use Chandler\Session\Session;
 use Chandler\Security\Authenticator;
 use Latte\Engine as TemplatingEngine;
+use Nette\InvalidStateException as ISE;
 use openvk\Web\Models\Entities\IP;
 use openvk\Web\Themes\Themepacks;
 use openvk\Web\Models\Repositories\{IPs, Users, APITokens, Tickets, Reports, CurrentUser, Posts};
@@ -74,7 +75,6 @@ abstract class OpenVKPresenter extends SimplePresenter
     protected function logInUserWithToken(): void
     {
         $header = $_SERVER["HTTP_AUTHORIZATION"] ?? "";
-        $token;
 
         preg_match("%Bearer (.*)$%", $header, $matches);
         $token = $matches[1] ?? "";
@@ -130,7 +130,7 @@ abstract class OpenVKPresenter extends SimplePresenter
         }
 
         if ($throw) {
-            throw new SecurityPolicyViolationException("Permission error");
+            throw new ISE("Permission error");
         } else {
             $this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment"));
         }
diff --git a/Web/Presenters/SearchPresenter.php b/Web/Presenters/SearchPresenter.php
index 102a6435..ee1b62a0 100644
--- a/Web/Presenters/SearchPresenter.php
+++ b/Web/Presenters/SearchPresenter.php
@@ -70,7 +70,7 @@ final class SearchPresenter extends OpenVKPresenter
                 case 'marital_status':
                 case 'polit_views':
                     if ((int) $param_value == 0) {
-                        continue;
+                        break;
                     }
                     $parameters[$param_name] = $param_value;
 
@@ -96,7 +96,7 @@ final class SearchPresenter extends OpenVKPresenter
                     # дай бог работал этот case
                 case 'from_me':
                     if ((int) $param_value != 1) {
-                        continue;
+                        break;
                     }
                     $parameters['from_me'] = $this->user->id;
 
diff --git a/Web/Presenters/SupportPresenter.php b/Web/Presenters/SupportPresenter.php
index 2ab2de2b..c9267b18 100644
--- a/Web/Presenters/SupportPresenter.php
+++ b/Web/Presenters/SupportPresenter.php
@@ -314,17 +314,20 @@ final class SupportPresenter extends OpenVKPresenter
         $comment = $this->comments->get($id);
 
         if ($this->user->id !== $comment->getTicket()->getUser()->getId()) {
-            exit(header("HTTP/1.1 403 Forbidden"));
+            header("HTTP/1.1 403 Forbidden");
+            exit();
         }
 
         if ($mark !== 1 && $mark !== 2) {
-            exit(header("HTTP/1.1 400 Bad Request"));
+            header("HTTP/1.1 400 Bad Request");
+            exit();
         }
 
         $comment->setMark($mark);
         $comment->save();
 
-        exit(header("HTTP/1.1 200 OK"));
+        header("HTTP/1.1 200 OK");
+        exit();
     }
 
     public function renderQuickBanInSupport(int $id): void
diff --git a/Web/Presenters/TopicsPresenter.php b/Web/Presenters/TopicsPresenter.php
index dadbb300..04987b6e 100644
--- a/Web/Presenters/TopicsPresenter.php
+++ b/Web/Presenters/TopicsPresenter.php
@@ -6,6 +6,7 @@ namespace openvk\Web\Presenters;
 
 use openvk\Web\Models\Entities\{Topic, Club, Comment, Photo, Video};
 use openvk\Web\Models\Repositories\{Topics, Clubs};
+use Nette\InvalidStateException as ISE;
 
 final class TopicsPresenter extends OpenVKPresenter
 {
@@ -112,9 +113,6 @@ final class TopicsPresenter extends OpenVKPresenter
                 $video = null;
                 if ($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) {
                     $album = null;
-                    if ($wall > 0 && $wall === $this->user->id) {
-                        $album = (new Albums())->getUserWallAlbum($wallOwner);
-                    }
 
                     $photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album);
                 }
diff --git a/Web/Presenters/VKAPIPresenter.php b/Web/Presenters/VKAPIPresenter.php
index 61ee33fa..2ab0818a 100644
--- a/Web/Presenters/VKAPIPresenter.php
+++ b/Web/Presenters/VKAPIPresenter.php
@@ -273,7 +273,7 @@ final class VKAPIPresenter extends OpenVKPresenter
             }
         }
 
-        define("VKAPI_DECL_VER", $this->requestParam("v") ?? "4.100", false);
+        define("VKAPI_DECL_VER", $this->requestParam("v") ?? "4.100");
 
         try {
             $res = $handler->{$method}(...$params);
diff --git a/Web/Presenters/WallPresenter.php b/Web/Presenters/WallPresenter.php
index b64c2af9..79b36c2e 100644
--- a/Web/Presenters/WallPresenter.php
+++ b/Web/Presenters/WallPresenter.php
@@ -265,8 +265,11 @@ final class WallPresenter extends OpenVKPresenter
         $this->assertUserLoggedIn();
         $this->willExecuteWriteAction();
 
-        $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1))
-                     ?? $this->flashFail("err", tr("failed_to_publish_post"), tr("error_4"));
+        $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1));
+
+        if ($wallOwner === null) {
+            $this->flashFail("err", tr("failed_to_publish_post"), tr("error_4"));
+        }
 
         if ($wallOwner->isBanned()) {
             $this->flashFail("err", tr("error"), tr("forbidden"));
@@ -568,8 +571,11 @@ final class WallPresenter extends OpenVKPresenter
         }
         $user = $this->user->id;
 
-        $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1))
-                     ?? $this->flashFail("err", tr("failed_to_delete_post"), tr("error_4"));
+        $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1));
+
+        if ($wallOwner === null) {
+            $this->flashFail("err", tr("failed_to_delete_post"), tr("error_4"));
+        }
 
         if ($wallOwner->isBanned()) {
             $this->flashFail("err", tr("error"), tr("forbidden"));
diff --git a/Web/Themes/Themepack.php b/Web/Themes/Themepack.php
index be2b2c11..01e41a3b 100644
--- a/Web/Themes/Themepack.php
+++ b/Web/Themes/Themepack.php
@@ -128,6 +128,6 @@ class Themepack
             throw new Exceptions\IncompatibleThemeException("Theme is built for newer OVK (themeEngine" . $manifest->openvk_version . ")");
         }
 
-        return new static($manifest->id, $manifest->version, (bool) ($manifest->inherit_master ?? true), (bool) ($manifest->override_templates ?? false), (bool) ($manifest->enabled ?? true), (object) $manifest->metadata);
+        return new Themepack($manifest->id, $manifest->version, (bool) ($manifest->inherit_master ?? true), (bool) ($manifest->override_templates ?? false), (bool) ($manifest->enabled ?? true), (object) $manifest->metadata);
     }
 }
diff --git a/Web/Themes/Themepacks.php b/Web/Themes/Themepacks.php
index 1907553a..7acc9dcd 100644
--- a/Web/Themes/Themepacks.php
+++ b/Web/Themes/Themepacks.php
@@ -88,25 +88,6 @@ class Themepacks implements \ArrayAccess
 
     /* /ArrayAccess */
 
-    public function install(string $archivePath): bool
-    {
-        if (!file_exists($archivePath)) {
-            return false;
-        }
-
-        $tmpDir = mkdir(tempnam(OPENVK_ROOT . "/tmp/themepack_artifacts/", "themex_"));
-        try {
-            $archive = new \CabArchive($archivePath);
-            $archive->extract($tmpDir);
-
-            return $this->installUnpacked($tmpDir);
-        } catch (\Exception $e) {
-            return false;
-        } finally {
-            rmdir($tmpDir);
-        }
-    }
-
     public function uninstall(string $id): bool
     {
         if (!isset($loadedThemepacks[$id])) {
diff --git a/Web/Util/Bitmask.php b/Web/Util/Bitmask.php
index c34fcdc6..c8fb7504 100644
--- a/Web/Util/Bitmask.php
+++ b/Web/Util/Bitmask.php
@@ -78,7 +78,7 @@ class Bitmask
         } elseif (gettype($key) === "int") {
             $this->setByOffset($key, $data);
         } else {
-            throw new TypeError("Key must be either offset (int) or a string index");
+            throw new \TypeError("Key must be either offset (int) or a string index");
         }
 
         return $this;
@@ -89,7 +89,7 @@ class Bitmask
         if (gettype($key) === "string") {
             $key = $this->getOffsetByKey($key);
         } elseif (gettype($key) !== "int") {
-            throw new TypeError("Key must be either offset (int) or a string index");
+            throw new \TypeError("Key must be either offset (int) or a string index");
         }
 
         return $this->length === 1 ? $this->getBoolByOffset($key) : $this->getNumberByOffset($key);
diff --git a/Web/Util/DateTime.php b/Web/Util/DateTime.php
index 6a82fe88..102312ed 100644
--- a/Web/Util/DateTime.php
+++ b/Web/Util/DateTime.php
@@ -66,6 +66,7 @@ class DateTime
             case static::RELATIVE_FORMAT_LOWER:
                 return $this->zmdate();
             case static::RELATIVE_FORMAT_SHORT:
+            default:
                 return "";
         }
     }
diff --git a/Web/Util/Makima/Makima.php b/Web/Util/Makima/Makima.php
index 92bb277d..e903ad65 100644
--- a/Web/Util/Makima/Makima.php
+++ b/Web/Util/Makima/Makima.php
@@ -188,9 +188,9 @@ class Makima
 
                 $tries = [];
 
-                $firstLine;
-                $secondLine;
-                $thirdLine;
+                $firstLine = null;
+                $secondLine = null;
+                $thirdLine = null;
 
                 # Try one line:
                 $tries[$firstLine = $count] = [$this->calculateMultiThumbsHeight($ratiosCropped, $maxWidth, $marginWidth)];
@@ -234,7 +234,7 @@ class Makima
                         }
                     }
 
-                    if (!$optimalConfiguration || $confDigff < $optimalDifference) {
+                    if (!$optimalConfiguration || $confDiff < $optimalDifference) {
                         $optimalConfiguration = $config;
                         $optimalDifference    = $confDiff;
                     }
diff --git a/bootstrap.php b/bootstrap.php
index 42241ca4..3fac8214 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -478,8 +478,8 @@ return (function () {
     define('YEAR', 365 * DAY);
 
     define("nullptr", null);
-    define("OPENVK_DEFAULT_INSTANCE_NAME", "OpenVK", false);
-    define("OPENVK_VERSION", "Altair Preview ($ver)", false);
-    define("OPENVK_DEFAULT_PER_PAGE", 10, false);
-    define("__OPENVK_ERROR_CLOCK_IN_FUTURE", "Server clock error: FK1200-DTF", false);
+    define("OPENVK_DEFAULT_INSTANCE_NAME", "OpenVK");
+    define("OPENVK_VERSION", "Altair Preview ($ver)");
+    define("OPENVK_DEFAULT_PER_PAGE", 10);
+    define("__OPENVK_ERROR_CLOCK_IN_FUTURE", "Server clock error: FK1200-DTF");
 });
diff --git a/chandler_loader.php b/chandler_loader.php
new file mode 100644
index 00000000..7566aecc
--- /dev/null
+++ b/chandler_loader.php
@@ -0,0 +1,10 @@
+#!/usr/bin/env php
+<?php
+
+declare(strict_types=1);
+
+namespace openvk;
+
+$_SERVER["HTTP_ACCEPT_LANGUAGE"] = false;
+$bootstrap = require(__DIR__ . "/../../../chandler/Bootstrap.php");
+$bootstrap->ignite(true);
diff --git a/composer.json b/composer.json
index 1020a4f7..8148e237 100644
--- a/composer.json
+++ b/composer.json
@@ -1,7 +1,8 @@
 {
     "scripts": {
         "fix": "php-cs-fixer fix",
-        "lint": "php-cs-fixer fix --dry-run --diff --verbose"
+        "lint": "php-cs-fixer fix --dry-run --diff --verbose",
+        "analyse": "phpstan analyse --memory-limit 1G"
     },
     "require": {
         "php": "~7.3||~8.1",
@@ -28,6 +29,7 @@
     },
     "minimum-stability": "beta",
     "require-dev": {
-        "friendsofphp/php-cs-fixer": "^3.68"
+        "friendsofphp/php-cs-fixer": "^3.68",
+        "phpstan/phpstan": "^2.1"
     }
 }
diff --git a/composer.lock b/composer.lock
index 3e7e2d72..9b27d119 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "b92d2ddd207f394a31c429c65d1785a7",
+    "content-hash": "fe88a04383a75cc5c6591abac3128201",
     "packages": [
         {
             "name": "al/emoji-detector",
@@ -3092,6 +3092,64 @@
             ],
             "time": "2025-01-30T17:00:50+00:00"
         },
+        {
+            "name": "phpstan/phpstan",
+            "version": "2.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpstan/phpstan.git",
+                "reference": "7d08f569e582ade182a375c366cbd896eccadd3a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpstan/phpstan/zipball/7d08f569e582ade182a375c366cbd896eccadd3a",
+                "reference": "7d08f569e582ade182a375c366cbd896eccadd3a",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.4|^8.0"
+            },
+            "conflict": {
+                "phpstan/phpstan-shim": "*"
+            },
+            "bin": [
+                "phpstan",
+                "phpstan.phar"
+            ],
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "PHPStan - PHP Static Analysis Tool",
+            "keywords": [
+                "dev",
+                "static analysis"
+            ],
+            "support": {
+                "docs": "https://phpstan.org/user-guide/getting-started",
+                "forum": "https://github.com/phpstan/phpstan/discussions",
+                "issues": "https://github.com/phpstan/phpstan/issues",
+                "security": "https://github.com/phpstan/phpstan/security/policy",
+                "source": "https://github.com/phpstan/phpstan-src"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/ondrejmirtes",
+                    "type": "github"
+                },
+                {
+                    "url": "https://github.com/phpstan",
+                    "type": "github"
+                }
+            ],
+            "time": "2025-01-21T14:54:06+00:00"
+        },
         {
             "name": "psr/event-dispatcher",
             "version": "1.0.0",
diff --git a/locales/kk.strings b/locales/kk.strings
index e3c08cae..9195a17b 100755
--- a/locales/kk.strings
+++ b/locales/kk.strings
@@ -45,8 +45,8 @@
 "register_meta_desc" = "$1 желісіне тіркеліңіз!";
 "register_referer_meta_title" = "$1 сізді $2 желіне шақырады!";
 "register_referer_meta_desc" = "$1 және одан көп пайдаланушылармен $2 желісінде қосылыңыз!";
-"registration_welcome_1" = "is a universal colleague search tool based on the VKontakte structure. дегеніміз — VK құрылымына негізделген әмбебап әріптестерді іздеу құралы";
-"registration_welcome_2" = "Достар, сыныптастар, көршілер мен әріптестер үнемі байланыста болғанын қалаймыз.";
+"registration_welcome_1" = "дегеніміз — VK құрылымына негізделген әмбебап әріптестерді іздеу құралы";
+"registration_welcome_2" = "Біз достар, сыныптастар, көршілер мен әріптестер үнемі байланыста болғанын қалаймыз.";
 "users" = "Пайдаланушылар";
 "other_fields" = "Қалғаны";
 
diff --git a/openvkctl b/openvkctl
index 1ed5f3be..39edfecf 100755
--- a/openvkctl
+++ b/openvkctl
@@ -7,9 +7,7 @@ namespace openvk;
 
 use Symfony\Component\Console\Application;
 
-$_SERVER["HTTP_ACCEPT_LANGUAGE"] = false;
-$bootstrap = require(__DIR__ . "/../../../chandler/Bootstrap.php");
-$bootstrap->ignite(true);
+require(__DIR__ . "/chandler_loader.php");
 
 $application = new Application();
 $application->add(new CLI\RebuildImagesCommand());
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 00000000..4831b315
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,14 @@
+parameters:
+    level: 0
+    paths:
+        - CLI
+        - ServiceAPI
+        - VKAPI
+        - Web
+        - bootstrap.php
+        - openvkctl
+        - chandler_loader.php
+    
+    bootstrapFiles:
+        - chandler_loader.php
+        - bootstrap.php