{"id":6118,"date":"2019-05-06T01:01:01","date_gmt":"2019-05-06T01:01:01","guid":{"rendered":"https:\/\/alexrusin.com\/?p=6118"},"modified":"2019-05-06T01:49:27","modified_gmt":"2019-05-06T01:49:27","slug":"setting-up-a-php-application","status":"publish","type":"post","link":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/","title":{"rendered":"Setting up a PHP Application"},"content":{"rendered":"\n

When setting up a new PHP application one should consider how to manage certain housekeeping tasks. The most important tasks to my mind are:<\/p>\n\n\n\n

  1. Exception handling. Uncaught exceptions should be taken care of in a top level exception handler, logged, and the website administrator should be notified.<\/li>
  2. Logging. It is important to log not only errors and exceptions, but also normally occurring events of importance, such as payloads of API responses.<\/li>
  3. Mail notification. Although your application may not send out transactional emails, it is a good idea to email administrator about critical events or exceptions that may happen when application is running.<\/li><\/ol>\n\n\n\n

    If you use a framework such as Laravel, the above tasks are usually taken care for you by the framework. Although frameworks are great, sometimes they can be an overkill and the best way to go is to use piece meal approach. Let’s look at how to approach the above tasks by using PHP libraries. We will be using exception handling application<\/a> as an example. <\/p>\n\n\n\n

    Let’s take a look at the application structure and composer.json<\/em> file.<\/p>\n\n\n\n

    \"\"
    Application structure and composer.json<\/em> file<\/figcaption><\/figure><\/div>\n\n\n\n

    The application has a simple composer.json<\/em> file that defines PSR-4 autoloading and requires Whoops, Monolog, SwiftMailer, and Mandrill libraries. Before jumping into the application internals let’s take a look at configuration file config.php<\/em> located in config<\/strong> folder.<\/p>\n\n\n\n

    <?php\n\nreturn [\n    'env' => 'production',\n    \/\/ values for mail_driver: smtp, mandrill, array\n    'mail_driver' => 'smtp',\n    'mandrill_key' => 'mandrill-key',\n    'mail_host' => 'smtp.mailtrap.io',\n    'mail_port' => '2525',\n    'mail_username' => 'username',\n    'mail_password' => 'password',\n    'mail_from_address' => 'info@app.com',\n    'mail_to_admin' => 'admin@admin.com'\n];<\/pre>\n\n\n\n

    The configuration file defines values for application environment, mail driver, and SMTP and API credentials for the mailers. In order to use the configuration values in the application, we will use config class rather than a global config variable. Below is the code for the Config.php <\/em>class.<\/p>\n\n\n\n

    <?php \n\nnamespace Apr\\ExceptionHandling;\n\nabstract class Config\n{\n    private static $config;\n\n    private static function getConfig() {\n\n        if (self::$config) {\n            return self::$config;\n        }\n        \n        return self::$config = require __DIR__ . '\/..\/config\/config.php';\n      \n    }\n\n    public static function get($property) \n    {\n        if (!array_key_exists($property, self::getConfig())) {\n            return;\n        }\n\n        return self::getConfig()[$property];\n    }\n}<\/pre>\n\n\n\n

    Config.php<\/em> is a utility helper class that wraps configuration file. Now we can get values from configuration file by statically calling get function. For example to get ‘env’ variable we can call Config::get(‘env’).<\/em><\/p>\n\n\n\n

    A similar helper Log.php<\/em> class wraps an instance of Monolog logger. <\/p>\n\n\n\n

    <?php \n\nnamespace Apr\\ExceptionHandling;\n\nuse DateTime;\nuse Monolog\\Logger;\nuse Monolog\\Handler\\StreamHandler;\nuse Monolog\\Formatter\\LineFormatter;\n\nabstract class Log\n{\n    private static $logger;\n\n    private static function getLogger()\n    {\n        if (self::$logger) {\n            return self::$logger;\n        }\n\n        $logger = new Logger(Config::get('env'));\n\n        $formatter = new LineFormatter(null, null, false, true);\n        $now = (new DateTime(\"now\"))->format('m_d_Y');\n\n        $handler = new StreamHandler(__DIR__ . \"\/..\/logs\/app_log_$now.log\", Logger::INFO);\n        $handler->setFormatter($formatter);\n\n        $logger->pushHandler($handler);\n\n        return self::$logger = $logger;\n    }\n\n    public static function __callStatic($name, $arguments)\n    {\n        if (empty($arguments)) {\n           throw new \\InvalidArgumentException(\"There is no message to log\");\n        }\n\n        $message = $arguments[0];\n\n        $logger = self::getLogger();\n\n        $logger->$name($message);\n    }\n}<\/pre>\n\n\n\n

    The above class creates an instance of Monolog logger and uses __callStatic magic method to dynamically call logging methods on the logger. So, we can use Log::info(‘Logging…’) or Log::alert(‘Error…’).<\/em> You can also create another stream handler to log errors in a separate error log file. Another thing to note is that if no message is passed to a logging function, InvalidArgumentException <\/em>will be thrown.<\/p>\n\n\n\n

    Now it is a good time to look at index.php file <\/em>located in public folder.<\/p>\n\n\n\n

    <?php\n\nrequire __DIR__ . '\/..\/bootstrap.php';\n\nuse Apr\\ExceptionHandling\\Log;\n\nLog::info('test log');\n\nLog::info();\n\necho 'Hello';<\/pre>\n\n\n\n

    index.php<\/em> file contains a simple sample code that first will write a log entry in our log file and them attempt to illegally use Log::info()<\/em> method that will cause InvalidArgumentException<\/em> to be thrown. Since the code does not user try..catch<\/em> block, this exception will bubble up and its handling will take place in bootstrap.php<\/em> file.<\/p>\n\n\n\n

    <?php\n\nrequire __DIR__ . '\/vendor\/autoload.php';\n\nuse Apr\\ExceptionHandling\\Config;\nuse Apr\\ExceptionHandling\\Exceptions\\ExceptionHandler;\n\n$whoops = new \\Whoops\\Run;\n\nif (Config::get('env') === 'develop') {\n    $whoops->pushHandler(new \\Whoops\\Handler\\PrettyPageHandler);\n}\n\n$whoops->pushHandler(function ($exception, $inspector, $run) {\n    $exceptionHandler = new ExceptionHandler;\n    $exceptionHandler->report($exception);\n});\n\n$whoops->register();<\/pre>\n\n\n\n

    Besides requiring composer’s autoload.php<\/em> file, bootsrtap.php<\/em> also contains logic that will use Whoop’s PrettyPageHandler<\/em> to display a nice exception page below.<\/p>\n\n\n\n

    \"\"
    Whoop’s exception page<\/figcaption><\/figure><\/div>\n\n\n\n

    The code will also use our custom ExceptionHandler<\/em> class to report the exception. Let’s take a look at that class.<\/p>\n\n\n\n

    <?php\n\nnamespace Apr\\ExceptionHandling\\Exceptions;\n\nuse Apr\\ExceptionHandling\\Log;\nuse Apr\\ExceptionHandling\\Config;\nuse Apr\\ExceptionHandling\\Mailer\\Mail;\n\nclass ExceptionHandler \n{\n    public function report($exception) \n    {\n        Log::error($exception);\n\n        if (Config::get('env') === 'production') \n        {\n            Mail::to(Config::get('mail_to_admin'))\n                ->from(Config::get('mail_from_address'))\n                ->subject('Exception occured')\n                ->message($exception)\n                ->send();\n            \n            throw $exception;\n            \/\/ or display 500 error page\n        }\n    }\n}<\/pre>\n\n\n\n

    ExceptionHandler::report()<\/em> method logs “bubbled up” exception and if the application in production emails it to the site administrator. Mail<\/em> class has a fluent interface to create an email and mail the exception.<\/p>\n\n\n\n

    <?php\n\nnamespace Apr\\ExceptionHandling\\Mailer;\n\nuse Apr\\ExceptionHandling\\Log;\n\nclass Mail\n{\n    public $to;\n    public $from;\n    public $subject = 'Email Notification';\n    public $message;\n\n    public static function to($addresses) \n    {\n        if (!is_array($addresses)) {\n            $to = [$addresses];\n        } else {\n            $to = $addresses;\n        }\n\n        $instance = new self;\n        $instance->to = $to;\n        return $instance;\n    }\n\n    public function from($addresses) \n    {\n        if (!is_array($addresses)) {\n            $this->from = [$addresses];\n        } else {\n            $this->from = $addresses;\n        }\n\n        return $this;\n    }\n\n    public function message($message)\n    {\n        $this->message = $message;\n        return $this;\n    }\n\n    public function subject($subject)\n    {\n        $this->subject = $subject;\n        return $this;\n    }\n\n    public function send()\n    {\n        $mailer = new Mailer;\n\n        try {\n            $mailer->send($this);\n        } catch (\\Throwable $e) {\n            Log::error($e);\n        }\n\n        \n    }\n}<\/pre>\n\n\n\n

    Mail::to()<\/em> method creates an instance of Mail<\/em> class and each chained method changes class properties and returns the instance back. Finally Mail::send()<\/em> creates an instance of a mailer and sends the email notification.<\/p>\n\n\n\n

    <?php\n\nnamespace Apr\\ExceptionHandling\\Mailer;\n\nuse Apr\\ExceptionHandling\\Config;\nuse Apr\\ExceptionHandling\\Log;\n\nclass Mailer\n{\n    public function send($mail)\n    {\n        if (Config::get('mail_driver') === 'smtp') {\n\n            $transport = (new \\Swift_SmtpTransport(Config::get('mail_host'), (int) Config::get('mail_port')))\n            ->setUsername(Config::get('mail_username'))\n            ->setPassword(Config::get('mail_password'))\n            ;\n\n            \/\/ Create the Mailer using your created Transport\n            $mailer = new \\Swift_Mailer($transport);\n\n            \/\/ Create a message\n            $message = (new \\Swift_Message($mail->subject))\n            ->setFrom($mail->from)\n            ->setTo($mail->to)\n            ->setBody($mail->message)\n            ;\n\n            return $mailer->send($message);\n\n        } else if (Config::get('mail_driver') === 'mandrill') {\n            \n            $mandrill = new \\Mandrill(Config::get('mandrill_key'));\n\n            $to = [];\n            foreach ($mail->to as $address) {\n               $to[] = ['email' => $address]; \n            }\n\n            $message = [\n                'html' => \"<p>$mail->message<\/p>\",\n                'text' => $mail->message,\n                'subject' => $mail->subject,\n                'from_email' => $mail->from[0],\n                'to' => $to,\n            ];\n\n            $async = false;\n            $ip_pool = 'Main Pool';\n            $send_at = (new \\DateTime(\"now\"))->format('Y-m-d H:m:s');\n            return $mandrill->messages->send($message, $async, $ip_pool, $send_at);            \n        }\n\n        Log::info(json_encode($mail));\n    }\n}<\/pre>\n\n\n\n

    Mailer::send()<\/em> method depending on the configuration mail driver creates either SwiftMailer or Mandrill instance and sends out the mail. If the driver is not specified, or other than ‘mandrill’ or ‘smtp’, the method will json_encode<\/em> mail and log it to the log file. <\/p>\n\n\n\n

    It is also worth noting that Monolog has SwiftMailHandler. It is possible to configure the logger it in such a way that Log::critical(‘Error message..’)<\/em> will also email the exception. Although this approach may be more convenient, creating a separate mailer, to my mind, gives greater flexibility in choosing when to mail exceptions.<\/p>\n\n\n\n

    To summarize, we created a skeleton for a PHP application that has exception handling, logging and email features. <\/p>\n","protected":false},"excerpt":{"rendered":"

    When setting up a new PHP application one should consider how to manage certain housekeeping tasks such as exception handling, logging, and mail notifications.<\/p>\n","protected":false},"author":1,"featured_media":6122,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"footnotes":""},"categories":[10],"tags":[],"class_list":["post-6118","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-php-mysql-development"],"yoast_head":"\nSetting up a PHP Application | Alex Rusin Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Setting up a PHP Application | Alex Rusin Blog\" \/>\n<meta property=\"og:description\" content=\"When setting up a new PHP application one should consider how to manage certain housekeeping tasks such as exception handling, logging, and mail notifications.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/\" \/>\n<meta property=\"og:site_name\" content=\"Alex Rusin Blog\" \/>\n<meta property=\"article:published_time\" content=\"2019-05-06T01:01:01+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2019-05-06T01:49:27+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/blog.alexrusin.com\/wp-content\/uploads\/2019\/05\/structure_and_composer.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"787\" \/>\n\t<meta property=\"og:image:height\" content=\"482\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"alexrusin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"alexrusin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/\"},\"author\":{\"name\":\"alexrusin\",\"@id\":\"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a\"},\"headline\":\"Setting up a PHP Application\",\"datePublished\":\"2019-05-06T01:01:01+00:00\",\"dateModified\":\"2019-05-06T01:49:27+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/\"},\"wordCount\":706,\"publisher\":{\"@id\":\"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a\"},\"image\":{\"@id\":\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/blog.alexrusin.com\/wp-content\/uploads\/2019\/05\/structure_and_composer.jpg\",\"articleSection\":[\"PHP MySQL Development\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/\",\"url\":\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/\",\"name\":\"Setting up a PHP Application | Alex Rusin Blog\",\"isPartOf\":{\"@id\":\"https:\/\/blog.alexrusin.com\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/blog.alexrusin.com\/wp-content\/uploads\/2019\/05\/structure_and_composer.jpg\",\"datePublished\":\"2019-05-06T01:01:01+00:00\",\"dateModified\":\"2019-05-06T01:49:27+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#primaryimage\",\"url\":\"https:\/\/blog.alexrusin.com\/wp-content\/uploads\/2019\/05\/structure_and_composer.jpg\",\"contentUrl\":\"https:\/\/blog.alexrusin.com\/wp-content\/uploads\/2019\/05\/structure_and_composer.jpg\",\"width\":787,\"height\":482},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/blog.alexrusin.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Setting up a PHP Application\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/blog.alexrusin.com\/#website\",\"url\":\"https:\/\/blog.alexrusin.com\/\",\"name\":\"Alex Rusin\",\"description\":\"Web Development Blog\",\"publisher\":{\"@id\":\"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/blog.alexrusin.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a\",\"name\":\"alexrusin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.alexrusin.com\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/c36ef231f9e0b11371891eb84360f4bc?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/c36ef231f9e0b11371891eb84360f4bc?s=96&d=mm&r=g\",\"caption\":\"alexrusin\"},\"logo\":{\"@id\":\"https:\/\/blog.alexrusin.com\/#\/schema\/person\/image\/\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Setting up a PHP Application | Alex Rusin Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/","og_locale":"en_US","og_type":"article","og_title":"Setting up a PHP Application | Alex Rusin Blog","og_description":"When setting up a new PHP application one should consider how to manage certain housekeeping tasks such as exception handling, logging, and mail notifications.","og_url":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/","og_site_name":"Alex Rusin Blog","article_published_time":"2019-05-06T01:01:01+00:00","article_modified_time":"2019-05-06T01:49:27+00:00","og_image":[{"width":787,"height":482,"url":"https:\/\/blog.alexrusin.com\/wp-content\/uploads\/2019\/05\/structure_and_composer.jpg","type":"image\/jpeg"}],"author":"alexrusin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"alexrusin","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#article","isPartOf":{"@id":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/"},"author":{"name":"alexrusin","@id":"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a"},"headline":"Setting up a PHP Application","datePublished":"2019-05-06T01:01:01+00:00","dateModified":"2019-05-06T01:49:27+00:00","mainEntityOfPage":{"@id":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/"},"wordCount":706,"publisher":{"@id":"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a"},"image":{"@id":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#primaryimage"},"thumbnailUrl":"https:\/\/blog.alexrusin.com\/wp-content\/uploads\/2019\/05\/structure_and_composer.jpg","articleSection":["PHP MySQL Development"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/","url":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/","name":"Setting up a PHP Application | Alex Rusin Blog","isPartOf":{"@id":"https:\/\/blog.alexrusin.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#primaryimage"},"image":{"@id":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#primaryimage"},"thumbnailUrl":"https:\/\/blog.alexrusin.com\/wp-content\/uploads\/2019\/05\/structure_and_composer.jpg","datePublished":"2019-05-06T01:01:01+00:00","dateModified":"2019-05-06T01:49:27+00:00","breadcrumb":{"@id":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#primaryimage","url":"https:\/\/blog.alexrusin.com\/wp-content\/uploads\/2019\/05\/structure_and_composer.jpg","contentUrl":"https:\/\/blog.alexrusin.com\/wp-content\/uploads\/2019\/05\/structure_and_composer.jpg","width":787,"height":482},{"@type":"BreadcrumbList","@id":"https:\/\/blog.alexrusin.com\/setting-up-a-php-application\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/blog.alexrusin.com\/"},{"@type":"ListItem","position":2,"name":"Setting up a PHP Application"}]},{"@type":"WebSite","@id":"https:\/\/blog.alexrusin.com\/#website","url":"https:\/\/blog.alexrusin.com\/","name":"Alex Rusin","description":"Web Development Blog","publisher":{"@id":"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.alexrusin.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/blog.alexrusin.com\/#\/schema\/person\/a9005ca622862109b2c514050fbaaf9a","name":"alexrusin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.alexrusin.com\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/c36ef231f9e0b11371891eb84360f4bc?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/c36ef231f9e0b11371891eb84360f4bc?s=96&d=mm&r=g","caption":"alexrusin"},"logo":{"@id":"https:\/\/blog.alexrusin.com\/#\/schema\/person\/image\/"}}]}},"_links":{"self":[{"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/posts\/6118","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/comments?post=6118"}],"version-history":[{"count":21,"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/posts\/6118\/revisions"}],"predecessor-version":[{"id":6148,"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/posts\/6118\/revisions\/6148"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/media\/6122"}],"wp:attachment":[{"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/media?parent=6118"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/categories?post=6118"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.alexrusin.com\/wp-json\/wp\/v2\/tags?post=6118"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}