PWA: Service Worker

An important piece of installing a service worker in your application is the ability to update it. Here a brief example of registering the service worker and then instructing it to check for an updated version.


if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js').then(registration => {
// Check for updates
registration.update();
console.log('SW registered and update called: ', registration);
}).catch(registrationError => {
console.log(registrationError);
});
});
}

Docs:
Service Worker Lifecycles – Manual Updates

Syncing Forks Without Merge Commits II: CLI


# Add an upstream remote for the repo that you just forked from
git remote add upstream https://github.com/ORIGINAL_OWNER/ORIGINAL_REPOSITORY.git

# Fetch the branch info
git fetch upstream

# Check out the local branch you'd like to sync up
# Set it to track against the local master so we can push/pull with shorthand
git checkout master --set-upstream=origin/master

# merge in the changes from the upstream repo
# at this point you should fast-forward and be in sync with the remote branch
git merge upstream/master

# push the local sync up to your repo
# since we set this to track origin/master already there is no need to specify
git push

Docs:
Configuring A Remote For A Fork
Syncing A Fork

Angular: Environment Variables

Add your variable and make sure that you configure each environment.


// ./public_html/src/environments/environment.ts or environment.prod.ts
export const environment = {
production: false,
apiUrl: 'https://example.apiurl.dev'
};

Then import the environment variables and use where necessary.


// ./public_html/src/app/example.ts
import {environment} from '../environments/environment';

export class Example {
public envOptions = {
apiUrl : environment.apiUrl
// ...
}
// ...
}

M2: Enabling REST API access to your module

If your module is correctly implementing interfaces and preferences for them in its di.xml then enabling web API access to your object’s repository should look something like the example shown below. You have the option of setting an ACL resource on any particular endpoint or making it publicly accessible for unauthenticated usage (maybe on the frontend of your site via AJAX?).

File Location: app/code/Vendor/Module/etc/webapi.xml
Reference: http://devdocs.magento.com/guides/v2.2/extension-dev-guide/service-contracts/service-to-web-service.html

    
        <?xml version="1.0"?>
        <routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
            <!-- Example Service -->
            <route url="/V1/example" method="POST">
                <service class="TCMP\ExampleModule\Api\ExampleRepositoryInterface" method="save"/>
                <resources>
                    <resource ref="TCMP_ExampleModule::save"/>
                </resources>
            </route>
            <route url="/V1/example/:id" method="PUT">
                <service class="TCMP\ExampleModule\Api\ExampleRepositoryInterfacee" method="save"/>
                <resources>
                    <resource ref="TCMP_ExampleModule::save"/>
                </resources>
            </route>
            <route url="/V1/example/:id" method="DELETE">
                <service class="TCMP\ExampleModule\Api\ExampleRepositoryInterface" method="delete"/>
                <resources>
                    <resource ref="TCMP_ExampleModule::delete"/>
                </resources>
            </route>
            <route url="/V1/example" method="GET">
                <service class="TCMP\ExampleModule\Api\ExampleRepositoryInterface" method="getList"/>
                <resources>
                    <!-- allow public access to your api! -->
                    <resource ref="anonymous"/>
                </resources>
            </route>
            <route url="/V1/example/:id" method="GET">
                <service class="TCMP\ExampleModule\Api\ExampleRepositoryInterface" method="getById"/>
                <resources>
                    <!-- allow public access to your api! -->
                    <resource ref="anonymous"/>
                </resources>
            </route>
        </routes>
    

No syntax highlighting on this one. Not sure which part of this liked me trying to post XML the least…but that’s added to the backlog now! Thankfully < XMP > hasn’t been removed yet!

Reference: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/xmp

Utils: Laravel + phpDocs + Static Code Analysis

If you’re using an IDE like PHP Storm then this is a necessity to your project:

https://github.com/barryvdh/laravel-ide-helper

More Reading:

https://www.phpdoc.org/docs/latest/index.html

Also, if you use PHPStorm, add this to help improve your code quality:

Php Inspections (EA Extended) – static code analysis

M2: Delete Orders Extension

I released a little freebie this weekend, hope you enjoy. Feedback is welcome just open an issue or even better submit a pull request!

https://github.com/theycallmepepper/m2-deleteorders/

– Adds “Delete” to order actions in ACL
– Adds “Delete” to mass actions dropdown on Sales Order Grid
– Adds “Delete” to buttons on Sales Order View

M2: Debugging Playground continued..

After the last example of a bootstrapping Magento 2.x in a php file for some quick debugging I thought I’d clean things up a bit.

Below is the same basic example but more neatly packaged into a more useful class. Based on the *require* statement this should be placed relative to the index.php in your Magento root but that can easily be adjusted. This principle can be used for debugging or, if you’d like to get creative, to integrate your Magento installation directly with another application.

When the lass is instantiated the __construct method processes other methods that create a new instance of Magento, retrieve the object manager from it, and setup the application state (see _setAreaCode method notes). (read more below..)

//I should live in the root dir next to index.php
require __DIR__ . '/app/bootstrap.php';

/**
* Class Playground2
*/
class Playground2 {

/**
* @var \Magento\Framework\App\Bootstrap
*/
protected $bootstrap;

/**
* @var \Magento\Framework\App\State
*/
protected $state;

/**
* @var \Magento\Framework\App\ObjectManager
*/
protected $objManager;

/**
* Playground constructor.
*/
public function __construct()
{
$this->_loadBootstrap();
$this->_loadObjectManager();
$this->_loadAppState();
$this->_setAreaCode();
}

/**
* Bootstrap M2
*/
protected function _loadBootstrap()
{
$this->bootstrap = \Magento\Framework\App\Bootstrap::create( BP, $_SERVER );
}

/**
* Get the object manager so we can interact
* with classes and handle DI
*/
protected function _loadObjectManager()
{
$this->objManager = $this->bootstrap->getObjectManager();
}

/**
* Load the application state class
*/
protected function _loadAppState()
{
$this->state = $this->objManager->get( '\Magento\Framework\App\State' );
}

/**
* Set the proper area code
*
* AREA_GLOBAL
* AREA_FRONTEND
* AREA_ADMIN
* AREA_ADMINHTML
* AREA_DOC
* AREA_CRONTAB
* AREA_WEBAPI_REST
* AREA_WEBAPI_SOAP
*/
protected function _setAreaCode()
{
$this->state->setAreaCode( \Magento\Framework\App\Area::AREA_FRONTEND );
}

/**
* And an example debugging method
*/
public function echoCategoryName()
{
/** @var \Magento\Catalog\Model\Category $_categoryModel */
$_categoryModel = $this->objManager->get( 'Magento\Catalog\Model\Category' );

$_category = $_categoryModel->load( 1 );

echo $_category->getName();
}

}

$playground = new Playground2();

$playground->echoCategoryName();

Once we have a new instance of Magento’s object manager ready to go we can set up methods for whatever we need to debug. In this case we’ve used the object manager to load the root category and echo out its name with:


$playground->echoCategoryName();
.

If you wanted to retrofit this class a bit you could expose the object manager…

public function getObjectManager()
{
return $this->objManager();
}

… and then use this as an interface to load Magento (or extension) classes in your application. (or do more debugging without writing methods like the previous example, but we were cleaning things up, right?)

Posted here also: https://gist.github.com/theycallmepepper/8ee69bc84a14b5a6c787da650e045879

M2: Debugging Playground

While I’m sure we all agree that your code should live inside an extension, it can be still helpful to interact with something a little more directly or isolated from some other components when debugging. This is where the M1 style “playground script” comes in handy. Note that several things have changed including how models/helpers/etc are accessed, and how the application itself is bootstrapped or more specifically how the object manager is accessed.

//I should live in the root dir next to index.php
require __DIR__ . '/app/bootstrap.php';

//Load the application bootstrapper
$bootstrap = \Magento\Framework\App\Bootstrap::create( BP, $_SERVER );

//Then get the object manager so we can interact with classes and get valid DI
$obj = $bootstrap->getObjectManager();

//Set the application state
/** @var \Magento\Framework\App\State $appState */
$appState = $obj->get( 'Magento\Framework\App\State' );
$appState->setAreaCode( \Magento\Framework\App\Area::AREA_FRONTEND );

//Instead of setting a specific code lets use a constant
//A reference of what else is available:
//AREA_GLOBAL
//AREA_FRONTEND
//AREA_ADMIN
//AREA_ADMINHTML
//AREA_DOC
//AREA_CRONTAB
//AREA_WEBAPI_REST
//AREA_WEBAPI_SOAP

//Get the repository responsible for loading category models
//Just because we're not working in a proper extension
//Doesn't mean we shouldn't load things the right way...

/** @var \Magento\Catalog\Model\CategoryRepository $_categoryRepo */
$_categoryRepo = $obj->get( 'Magento\Catalog\Model\CategoryRepository' );

//See the previous post for a better example of using repos
//Though this particular repo works a little different
//as it only offers the following methods:
//->get('category_id','store_id');
//->save($category);
//->delete($category);
//->deleteByIdentifier('category_id');

//Load by category_id & store_id
$_category = $_categoryRepo->get( 1, 0 );

//Now we have our model...
echo $_category->getName();

M2: Invalidating Caches

When saving objects in your custom module it may be beneficial to indicate that a cache type should be cleared. ┬áThis can be achieved using the TypeListInterface from the Framework’s Cache Module. Furthermore with this interface you can also retrieve the cache type labels, a list of other caches that have been invalidated, or clean a cache type by code.

<?php

namespace Vendor\Module\Folder;

use Magento\Framework\App\Cache\TypeListInterface;

/**
 * Class Example
 *
 * @package Vendor\Module\Folder
 */
class Example {

   /**
   * @var TypeListInterface
   */
   protected $_cacheTypesList;

   /**
    * Example constructor.
    *
    * @param TypeListInterface $_cacheTypesList
    */
   public function __construct( TypeListInterface $_cacheTypesList )
   {
      $this->_cacheTypesList = $_cacheTypesList;
   }

   /**
    * Mark specific cache type(s) as invalidated
    *
    * @param $type
    */
   public function invalidateCache( $type )
   {
      $this->_cacheTypesList->invalidate( $type );
   }

   /**
    * Get information about all declared cache types
    *
    * @return array
    */
   public function getCacheTypes()
   {
      /* Example structure for cache type array
       $_types = [
         'CACHE TYPE CODE' =>
          (object)[ //Magento\Framework\DataObject
                    'id' =>(int)'',
                    'cache_type'=>(string) '',
                    'description'=>(string)'',
                    'tags'=>(string)'',
                     'status'=>(int)'',
             ]
      ];*/
      return $this->_cacheTypesList->getTypes();
   }

}

M2: Add a CMS block to your layout

Add a CMS block to your layout XML:

<referenceContainer name="containerName">
    <block class="Magento\Cms\Block\Block" name="block_name_in_layout" as="block.name.in.layout">
        <arguments>
            <argument name="block_id" xsi:type="string">block_identifier</argument>
        </arguments>
    </block>
</referenceContainer>