Skip to content

Instantly share code, notes, and snippets.

@Anubarak
Last active March 17, 2019 13:42
Show Gist options
  • Save Anubarak/baf80b6ed1db88e0f82b85771b4701e4 to your computer and use it in GitHub Desktop.
Save Anubarak/baf80b6ed1db88e0f82b85771b4701e4 to your computer and use it in GitHub Desktop.
<?php
/**
* ElementCreate Action: this class handles Updating and Creating Craft elements,
*
* API for Craft CMS 3.x
*
* Created with PhpStorm.
*
* @link https://github.com/Anubarak/
* @copyright Copyright (c) 2019 Robin Schambach
*/
namespace modules\api\base\base;
use Craft;
use craft\base\ElementInterface;
use craft\base\Model;
use craft\helpers\Json;
use craft\web\Response;
use yii\helpers\ArrayHelper;
use yii\helpers\Url;
use yii\rest\Action;
use yii\web\ServerErrorHttpException;
class ElementCreateAction extends Action
{
/**
* @var string the scenario to be assigned to the new model before it is validated and saved.
*/
public $scenario = Model::SCENARIO_DEFAULT;
/**
* @var string the name of the view action. This property is need to create the URL when the model is successfully
* created.
*/
public $viewAction = 'view';
/**
* Creates a new model.
* @throws \Throwable
* @throws \craft\errors\ElementNotFoundException
* @throws \yii\base\Exception
* @throws \yii\web\ServerErrorHttpException
* @return \craft\base\ElementInterface the model newly created
*/
public function run(): ElementInterface
{
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id);
}
// just for vue/axios, you'll normaly just use $data = Craft::$app->getRequest()->getBodyParams();
$data = Json::decode(Craft::$app->getRequest()->getRawBody());
if(empty($data)){
$data = Craft::$app->getRequest()->getBodyParams();
}
$id = $data['id'] ?? null;
if ($id !== null) {
$class = $this->modelClass; // modelClass is defined in the Controller
$model = Craft::$app->getElements()->getElementById((int)$id, $class);
} else {
/** @var \craft\base\Element $model */
$model = new $this->modelClass(
[
'scenario' => $this->scenario,
]
);
}
// set all write-able attributes
$safeAttributes = [];
foreach ($data as $key => $value){
if($model->canSetProperty($key)){
$safeAttributes[$key] = $value;
}
}
$model->setAttributes($safeAttributes);
// Save/Set all the custom fields
if($model::hasContent()){
$layout = $model->getFieldLayout();
if($layout !== null){
$fields = $layout->getFields();
$handles = ArrayHelper::getColumn($fields, 'handle');
foreach ($handles as $handle){
if(isset($data[$handle])){
$model->setFieldValue($handle, $data[$handle]);
}
}
}
}
if (Craft::$app->getElements()->saveElement($model)) {
$response = Craft::$app->getResponse();
$response->setStatusCode(201);
$id = $model->getId();
$response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true));
} elseif (!$model->hasErrors()) {
throw new ServerErrorHttpException('Failed to create the object for unknown reason.');
}
return $model;
}
}
<?php
/**
* This class is my "main" REST Controller, every other REST Controller extends this class
* it's purpose is to create a layer between Yii2 REST API that are all handled via ActiveControllers
* and Craft Elements
*
* API for Craft CMS 3.x
*
* Created with PhpStorm.
*
* @link https://github.com/Anubarak/
* @copyright Copyright (c) 2019 Robin Schambach
*/
namespace modules\api\base\base;
use Craft;
use craft\web\Response;
use modules\api\base\helpers\CorsHelper;
use modules\myspa\MYSPA;
use yii\rest\ActiveController as Controller;
/**
* Class ActiveController
* @package modules\api\base\base
* @since 12.02.2019
*
* @property array $serializeFields
* @property array $baseCriteria
*/
class ActiveController extends Controller
{
/**
* @var array List of allowed verbs for this Controller
*/
public $verbs = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
/**
* @var string|array the configuration for creating the serializer that formats the response data.
*/
public $serializer = ElementSerializer::class;
/**
* @var array
*/
public $serializeOptions = [];
public function actions()
{
$actions = parent::actions();
// overwrite all default actions via custom Actions, so every craft element type can use it
$actions['create'] = [
'class' => ElementCreateAction::class, // <--- overwrite all actions for Craft Elements, see the prev file
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
'scenario' => $this->createScenario,
];
$actions['update'] = [
'class' => ElementUpdateAction::class,
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
'scenario' => $this->createScenario,
];
$actions['delete'] = [
'class' => ElementDeleteAction::class,
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['view'] = [
'class' => ElementViewAction::class,
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['index'] = [
'class' => ElementIndexAction::class,
'modelClass' => $this->modelClass,
'baseCriteria' => $this->getBaseCriteria(),
'checkAccess' => [$this, 'checkAccess'],
];
$actions['options'] = [
'class' => ElementOptionsAction::class,
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
return $actions;
}
/**
* behaviors
*
* @throws \yii\base\Exception
* @return array
*
* @author Robin Schambach
*/
public function behaviors(): array
{
$behaviors = parent::behaviors();
// custom behavior to handle CORS requests
$behaviors[] = CorsHelper::getBehavior();
return $behaviors;
}
}
<?php
/**
* OrdersController To handle Orders via REST API, you can as well do the very same for
* Products
*
* API for Craft CMS 3.x
*
* Created with PhpStorm.
*
* @link https://github.com/Anubarak/
* @copyright Copyright (c) 2019 Robin Schambach
*/
namespace modules\api\v1\controllers;
use Craft;
use craft\commerce\elements\Order;
use craft\web\Response;
use modules\api\base\base\ActiveController;
use modules\api\base\base\FractalSerializer;
use modules\api\base\filters\AccessRule;
use modules\api\v1\transformers\OrderTransformer;
use modules\myspa\services\Stores;
use yii\filters\AccessControl;
class OrdersController extends ActiveController
{
// modelclass is craft\commerce\elements\Order;
public $modelClass = Order::class;
// I'm using fractal instead of the Yii2 Serializer because it's more easier, but I guess
// since you don't provide data and only want to receive it you won't need that
public $serializer = [
'class' => FractalSerializer::class,
'transformer' => OrderTransformer::class
];
public function behaviors(): array
{
$behaviors = parent::behaviors();
// include an access controller, You can take a look at Yii2 for other access Control methods,
// For example this blocks certain requests for authorized users
// see here for other methods https://www.yiiframework.com/doc/guide/2.0/en/rest-authentication
$behaviors['access'] = [
'class' => AccessControl::class,
'ruleConfig' => ['class' => AccessRule::class],
'only' => ['create', 'index', 'options', 'delete'],
'rules' => [
[
'allow' => true,
'actions' => ['index'],
'roles' => ['myspa-desk-orders'],
],
[
'allow' => true,
'actions' => ['create', 'update'],
'roles' => ['myspa-desk-orders-save'],
],
[
'allow' => true,
'actions' => ['delete'],
'roles' => ['myspa-desk-orders-delete'],
],
],
];
return $behaviors;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment