Replaces $ENTITY and $ACTION in docblocks to improve help text in the api explorer.
*/
private function addDocs(&$entity) {
$reflection = new \ReflectionClass("\\Civi\\Api4\\" . $entity['name']);
- $entity += ReflectionUtils::getCodeDocs($reflection);
+ $entity += ReflectionUtils::getCodeDocs($reflection, NULL, ['$ENTITY' => $entity['name']]);
unset($entity['package'], $entity['method']);
}
use Civi\Api4\Utils\ReflectionUtils;
/**
- * Get actions for an entity with a list of accepted params
+ * Get all API actions for the $ENTITY entity.
+ *
+ * Includes a list of accepted parameters for each action, descriptions and other documentation.
*/
class GetActions extends BasicGetAction {
if (is_object($action)) {
$this->_actions[$actionName] = ['name' => $actionName];
if ($this->_isFieldSelected('description', 'comment', 'see')) {
+ $vars = ['$ENTITY' => $this->getEntityName(), '$ACTION' => $actionName];
// Docblock from action class
- $actionDocs = ReflectionUtils::getCodeDocs($action->reflect());
+ $actionDocs = ReflectionUtils::getCodeDocs($action->reflect(), NULL, $vars);
unset($actionDocs['method']);
// Docblock from action factory function in entity class. This takes precedence since most action classes are generic.
if ($method) {
- $methodDocs = ReflectionUtils::getCodeDocs($method, 'Method');
- $actionDocs = $methodDocs + $actionDocs;
+ $methodDocs = ReflectionUtils::getCodeDocs($method, 'Method', $vars);
+ // Allow method doc to inherit class doc
+ if (strpos($method->getDocComment(), '@inheritDoc') !== FALSE && !empty($methodDocs['comment']) && !empty($actionDocs['comment'])) {
+ $methodDocs['comment'] .= "\n\n" . $actionDocs['comment'];
+ }
+ $actionDocs = array_filter($methodDocs) + $actionDocs;
}
$this->_actions[$actionName] += $actionDocs;
}
public function getParamInfo($param = NULL) {
if (!isset($this->_paramInfo)) {
$defaults = $this->getParamDefaults();
+ $vars = [
+ '$ENTITY' => $this->getEntityName(),
+ '$ACTION' => $this->getActionName(),
+ ];
+ // For actions like "getFields" and "getActions" they are not getting the entity itself.
+ // So generic docs will make more sense like this:
+ if (substr($vars['$ACTION'], 0, 3) === 'get' && substr($vars['$ACTION'], -1) === 's') {
+ $vars['$ENTITY'] = lcfirst(substr($vars['$ACTION'], 3, -1));
+ }
foreach ($this->reflect()->getProperties(\ReflectionProperty::IS_PROTECTED) as $property) {
$name = $property->getName();
if ($name != 'version' && $name[0] != '_') {
- $this->_paramInfo[$name] = ReflectionUtils::getCodeDocs($property, 'Property');
+ $this->_paramInfo[$name] = ReflectionUtils::getCodeDocs($property, 'Property', $vars);
$this->_paramInfo[$name]['default'] = $defaults[$name];
}
}
abstract class AbstractBatchAction extends AbstractQueryAction {
/**
- * Criteria for selecting items to process.
+ * Criteria for selecting $ENTITYs to process.
*
* @var array
* @required
namespace Civi\Api4\Generic;
/**
- * Base class for all "Create" api actions.
+ * Base class for all `Create` api actions.
*
* @method $this setValues(array $values) Set all field values from an array of key => value pairs.
* @method array getValues() Get field values.
abstract class AbstractCreateAction extends AbstractAction {
/**
- * Field values to set
+ * Field values to set for the new $ENTITY.
*
* @var array
*/
use Civi\Api4\Utils\SelectUtil;
/**
- * Base class for all "Get" api actions.
+ * Base class for all `Get` api actions.
*
* @package Civi\Api4\Generic
*
abstract class AbstractGetAction extends AbstractQueryAction {
/**
- * Fields to return. Defaults to all fields `["*"]`.
+ * Fields to return for each $ENTITY. Defaults to all fields `[*]`.
*
* Use the * wildcard by itself to select all available fields, or use it to match similarly-named fields.
* E.g. `is_*` will match fields named is_primary, is_active, etc.
*
- * Set to `["row_count"]` to return only the number of items found.
+ * Set to `["row_count"]` to return only the number of $ENTITYs found.
*
* @var array
*/
namespace Civi\Api4\Generic;
/**
- * Base class for all actions that need to fetch records (Get, Update, Delete, etc)
+ * Base class for all actions that need to fetch records (`Get`, `Update`, `Delete`, etc.).
*
* @package Civi\Api4\Generic
*
abstract class AbstractQueryAction extends AbstractAction {
/**
- * Criteria for selecting items.
- *
- * $example->addWhere('contact_type', 'IN', array('Individual', 'Household'))
+ * Criteria for selecting $ENTITYs.
*
+ * ```php
+ * $example->addWhere('contact_type', 'IN', ['Individual', 'Household'])
+ * ```
* @var array
*/
protected $where = [];
/**
- * Array of field(s) to use in ordering the results
+ * Array of field(s) to use in ordering the results.
*
* Defaults to id ASC
*
+ * ```php
* $example->addOrderBy('sort_name', 'ASC')
- *
+ * ```
* @var array
*/
protected $orderBy = [];
/**
- * Maximum number of results to return.
+ * Maximum number of $ENTITYs to return.
*
* Defaults to unlimited.
*
protected $limit = 0;
/**
- * Zero-based index of first result to return.
+ * Zero-based index of first $ENTITY to return.
*
- * Defaults to "0" - first record.
+ * Defaults to "0" - first $ENTITY found.
*
* @var int
*/
namespace Civi\Api4\Generic;
/**
- * Base class for all "Save" api actions.
+ * Base class for all `Save` api actions.
*
* @method $this setRecords(array $records) Set array of records to be saved.
* @method array getRecords()
abstract class AbstractSaveAction extends AbstractAction {
/**
- * Array of records.
+ * Array of $ENTITYs to save.
*
- * Should be in the same format as returned by Get.
+ * Should be in the same format as returned by `Get`.
*
* @var array
* @required
/**
* Array of default values.
*
- * These defaults will be applied to all records unless they specify otherwise.
+ * These defaults will be applied to all $ENTITYs unless they specify otherwise.
*
* @var array
*/
protected $defaults = [];
/**
- * Reload records after saving.
+ * Reload $ENTITYs after saving.
*
- * By default this api typically returns partial records containing only the fields
- * that were updated. Set reload to TRUE to do an additional lookup after saving
- * to return complete records.
+ * By default this action typically returns partial records containing only the fields
+ * that were updated. Set `reload` to `true` to do an additional lookup after saving
+ * to return complete values for every $ENTITY.
*
* @var bool
*/
namespace Civi\Api4\Generic;
/**
- * Base class for all "Update" api actions
+ * Base class for all `Update` api actions
*
* @method $this setValues(array $values) Set all field values from an array of key => value pairs.
* @method array getValues() Get field values.
protected $values = [];
/**
- * Reload objects after saving.
+ * Reload $ENTITYs after saving.
*
- * Setting to TRUE will load complete records and return them as the api result.
- * If FALSE the api usually returns only the fields specified to be updated.
+ * Setting to `true` will load complete records and return them as the api result.
+ * If `false` the api usually returns only the fields specified to be updated.
*
* @var bool
*/
}
/**
- * Add an item to the values array
+ * Add an item to the values array.
+ *
* @param string $fieldName
* @param mixed $value
* @return $this
use Civi\API\Exception\NotImplementedException;
/**
- * Basic action for deleting or performing some other task with a set of records. Ex:
+ * $ACTION one or more $ENTITYs.
*
- * $myAction = new BasicBatchAction('Entity', 'action', function($item) {
- * // Do something with $item
- * $return $item;
- * });
+ * $ENTITYs are selected based on criteria specified in `where` parameter (required).
*
* @package Civi\Api4\Generic
*/
/**
* BasicBatchAction constructor.
*
+ * ```php
+ * $myAction = new BasicBatchAction($entityName, $actionName, function($item) {
+ * // Do something with $item
+ * $return $item;
+ * });
+ * ```
+ *
* @param string $entityName
* @param string $actionName
* @param string|array $select
use Civi\API\Exception\NotImplementedException;
/**
- * Create a new object from supplied values.
+ * Create a new $ENTITY from supplied values.
*
- * This function will create 1 new object. It cannot be used to update existing objects. Use the Update or Replace actions for that.
+ * This action will create 1 new $ENTITY.
+ * It cannot be used to update existing $ENTITYs; use the `Update` or `Replace` actions for that.
*/
class BasicCreateAction extends AbstractCreateAction {
use Civi\API\Exception\NotImplementedException;
/**
- * Retrieve items based on criteria specified in the 'where' param.
+ * Retrieve $ENTITYs based on criteria specified in the `where` parameter.
*
- * Use the 'select' param to determine which fields are returned, defaults to *.
+ * Use the `select` param to determine which fields are returned, defaults to `[*]`.
*/
class BasicGetAction extends AbstractGetAction {
use Traits\ArrayQueryActionTrait;
use Civi\Api4\Utils\ActionUtil;
/**
- * Get information about an entity's fields.
+ * Lists information about fields for the $ENTITY entity.
*
* This field information is also known as "metadata."
*
* Note that different actions may support different lists of fields.
- * By default this will fetch the field list relevant to Get,
+ * By default this will fetch the field list relevant to `get`,
* but a different list may be returned if you specify another action.
*
* @method $this setLoadOptions(bool $value)
use Civi\Api4\Utils\ActionUtil;
/**
- * Given a set of records, will appropriately update the database.
+ * Replaces an existing set of $ENTITYs with a new one.
*
- * @method $this setRecords(array $records) Array of records.
+ * This will select a group of existing $ENTITYs based on the `where` parameter.
+ * Each will be compared with the $ENTITYs passed in as `records`:
+ *
+ * - $ENTITYs in `records` that don't already exist will be created.
+ * - Existing $ENTITYs that are included in `records` will be updated.
+ * - Existing $ENTITYs that are omitted from `records` will be deleted.
+ *
+ * @method $this setRecords(array $records) Set array of records.
* @method array getRecords()
- * @method $this setDefaults(array $defaults) Array of defaults.
+ * @method $this setDefaults(array $defaults) Set array of defaults.
* @method array getDefaults()
* @method $this setReload(bool $reload) Specify whether complete objects will be returned after saving.
* @method bool getReload()
class BasicReplaceAction extends AbstractBatchAction {
/**
- * Array of records.
+ * Array of $ENTITY records.
*
- * Should be in the same format as returned by Get.
+ * Should be in the same format as returned by `Get`.
*
* @var array
* @required
/**
* Array of default values.
*
- * Will be merged into $records before saving.
+ * Will be merged into `records` before saving.
+ *
+ * **Note:** Values from the `where` clause that use the `=` operator are _also_ saved into each record;
+ * those do not need to be repeated here.
*
* @var array
*/
protected $defaults = [];
/**
- * Reload records after saving.
+ * Reload $ENTITYs after saving.
*
- * By default this api typically returns partial records containing only the fields
- * that were updated. Set reload to TRUE to do an additional lookup after saving
- * to return complete records.
+ * By default this action typically returns partial records containing only the fields
+ * that were updated. Set `reload` to `true` to do an additional lookup after saving
+ * to return complete values for every $ENTITY.
*
* @var bool
*/
use Civi\Api4\Utils\ActionUtil;
/**
- * Create or update one or more records.
+ * $ACTION one or more $ENTITYs.
*
- * If creating more than one record with similar values, use the "defaults" param.
+ * If saving more than one new $ENTITY with similar values, use the `defaults` parameter.
*
- * Set "reload" if you need the api to return complete records.
+ * Set `reload` if you need the api to return complete $ENTITY records.
*/
class BasicSaveAction extends AbstractSaveAction {
use Civi\API\Exception\NotImplementedException;
/**
- * Update one or more records with new values.
+ * Update one or more $ENTITY with new values.
*
- * Use the where clause (required) to select them.
+ * Use the `where` clause (required) to select them.
*/
class BasicUpdateAction extends AbstractUpdateAction {
namespace Civi\Api4\Generic;
/**
- * Create a new object from supplied values.
+ * Create a new $ENTITY from supplied values.
*
- * This function will create 1 new object. It cannot be used to update existing objects. Use the Update or Replace actions for that.
+ * This action will create 1 new $ENTITY.
+ * It cannot be used to update existing $ENTITYs; use the `Update` or `Replace` actions for that.
*/
class DAOCreateAction extends AbstractCreateAction {
use Traits\DAOActionTrait;
namespace Civi\Api4\Generic;
/**
- * Delete one or more items, based on criteria specified in Where param (required).
+ * Delete one or more $ENTITYs.
+ *
+ * $ENTITYs are deleted based on criteria specified in `where` parameter (required).
*/
class DAODeleteAction extends AbstractBatchAction {
use Traits\DAOActionTrait;
namespace Civi\Api4\Generic;
/**
- * Retrieve items based on criteria specified in the 'where' param.
+ * Retrieve $ENTITYs based on criteria specified in the `where` parameter.
*
- * Use the 'select' param to determine which fields are returned, defaults to *.
+ * Use the `select` param to determine which fields are returned, defaults to `[*]`.
*
* Perform joins on other related entities using a dot notation.
*/
use Traits\DAOActionTrait;
/**
- * Fields to return. Defaults to all non-custom fields `["*"]`.
+ * Fields to return. Defaults to all non-custom fields `[*]`.
*
- * Use the dot notation to perform joins in the select clause, e.g. selecting `["*", "contact.*"]` from `Email::get()`
+ * Use the dot notation to perform joins in the select clause, e.g. selecting `['*', 'contact.*']` from `Email::get()`
* will select all fields for the email + all fields for the related contact.
*
* @var array
namespace Civi\Api4\Generic;
/**
- * Create or update one or more records.
+ * Create or update one or more $ENTITYs.
*
- * If creating more than one record with similar values, use the "defaults" param.
+ * If creating more than one $ENTITY with similar values, use the `defaults` param.
*
- * Set "reload" if you need the api to return complete records.
+ * Set `reload` if you need the api to return complete records for each saved $ENTITY.
*/
class DAOSaveAction extends AbstractSaveAction {
use Traits\DAOActionTrait;
namespace Civi\Api4\Generic;
/**
- * Update one or more records with new values.
+ * Update one or more $ENTITY with new values.
*
- * Use the where clause (required) to select them.
+ * Use the `where` clause (required) to select them.
*/
class DAOUpdateAction extends AbstractUpdateAction {
use Traits\DAOActionTrait;
* @param \Reflector|\ReflectionClass $reflection
* @param string $type
* If we are not reflecting the class itself, specify "Method", "Property", etc.
- *
+ * @param array $vars
+ * Variable substitutions to perform in the docblock
* @return array
*/
- public static function getCodeDocs($reflection, $type = NULL) {
- $docs = self::parseDocBlock($reflection->getDocComment());
+ public static function getCodeDocs($reflection, $type = NULL, $vars = []) {
+ $comment = $reflection->getDocComment();
+ if ($vars) {
+ $comment = str_replace(array_keys($vars), array_values($vars), $comment);
+ }
+ $docs = self::parseDocBlock($comment);
// Recurse into parent functions
if (isset($docs['inheritDoc']) || isset($docs['inheritdoc'])) {
}
if ($newReflection) {
// Mix in
- $additionalDocs = self::getCodeDocs($newReflection, $type);
+ $additionalDocs = self::getCodeDocs($newReflection, $type, $vars);
if (!empty($docs['comment']) && !empty($additionalDocs['comment'])) {
$docs['comment'] .= "\n\n" . $additionalDocs['comment'];
}
$info['params'][$param]['comment'] .= $line . "\n";
}
elseif ($num == 1) {
- $info['description'] = $line;
+ $info['description'] = ucfirst($line);
}
elseif (!$line) {
if (isset($info['comment'])) {
$info['comment'] .= "\n";
}
+ else {
+ $info['comment'] = NULL;
+ }
+ }
+ // For multi-line description.
+ elseif (count($info) === 1 && isset($info['description']) && substr($info['description'], -1) !== '.') {
+ $info['description'] .= ' ' . $line;
}
else {
$info['comment'] = isset($info['comment']) ? "{$info['comment']}\n$line" : $line;
}
}
if (isset($info['comment'])) {
- $info['comment'] = trim($info['comment']);
+ $info['comment'] = rtrim($info['comment']);
}
return $info;
}
<div ng-if="helpContent.see">
<strong>See:</strong>
<ul>
- <li ng-repeat="ref in helpContent.see">
- <a target="{{ formatRef(ref)[0] === '#' ? '_self' : '_blank'}}" ng-href="{{ formatRef(ref) }}">{{ ref }}</a>
- </li>
+ <li ng-repeat="ref in helpContent.see" ng-bind-html="ref"> </li>
</ul>
</div>
</div>
$scope.helpContent = helpContent;
} else {
$scope.helpTitle = title;
- $scope.helpContent = convertMarkdown(content);
+ $scope.helpContent = formatHelp(content);
}
};
// Sets the static help text (which gets overridden by mousing over other elements)
function setHelp(title, content) {
$scope.helpTitle = helpTitle = title;
- $scope.helpContent = helpContent = convertMarkdown(content);
+ $scope.helpContent = helpContent = formatHelp(content);
}
- function convertMarkdown(rawContent) {
+ // Convert plain-text help to markdown; replace variables and format links
+ function formatHelp(rawContent) {
+ function formatRefs(see) {
+ _.each(see, function(ref, idx) {
+ var match = ref.match(/^\\Civi\\Api4\\([a-zA-Z]+)$/);
+ if (match) {
+ ref = '#/explorer/' + match[1];
+ }
+ if (ref[0] === '\\') {
+ ref = 'https://github.com/civicrm/civicrm-core/blob/master' + ref.replace(/\\/i, '/') + '.php';
+ }
+ see[idx] = '<a target="' + (ref[0] === '#' ? '_self' : '_blank') + '" href="' + ref + '">' + see[idx] + '</a>';
+ });
+ }
var formatted = _.cloneDeep(rawContent);
if (formatted.description) {
formatted.description = marked(formatted.description);
if (formatted.comment) {
formatted.comment = marked(formatted.comment);
}
+ formatRefs(formatted.see);
return formatted;
}
- // Format the href for a @see help annotation
- $scope.formatRef = function(see) {
- var match = see.match(/^\\Civi\\Api4\\([a-zA-Z]+)$/);
- if (match) {
- return '#/explorer/' + match[1];
- }
- if (see[0] === '\\') {
- return 'https://github.com/civicrm/civicrm-core/blob/master' + see.replace(/\\/i, '/') + '.php';
- }
- return see;
- };
-
$scope.fieldHelp = function(fieldName) {
var field = getField(fieldName, $scope.entity, $scope.action);
if (!field) {
->indexBy('name');
$getParams = $result['get']['params'];
- $whereDescription = 'Criteria for selecting items.';
+ $whereDescription = 'Criteria for selecting Participants';
$this->assertEquals(TRUE, $getParams['checkPermissions']['default']);
- $this->assertEquals($whereDescription, $getParams['where']['description']);
+ $this->assertContains($whereDescription, $getParams['where']['description']);
}
public function testGet() {
/**
* This is the foo property.
*
- * In general, you can do nothing with it.
+ * - In general, you can do nothing with it.
*
* @var array
*/
namespace api\v4\Mock;
/**
- * Grandchild class
+ * Grandchild class for $ENTITY,
+ * with a 2-line description!
*
- * This is an extended description.
+ * This is an extended comment.
*
- * There is a line break in this description.
+ * There is a line break in this comment.
*
* @inheritdoc
*/
public function testGetDocBlockForClass() {
$grandChild = new MockV4ReflectionGrandchild();
$reflection = new \ReflectionClass($grandChild);
- $doc = ReflectionUtils::getCodeDocs($reflection);
+ $doc = ReflectionUtils::getCodeDocs($reflection, NULL, ['$ENTITY' => "Test"]);
$this->assertEquals(TRUE, $doc['internal']);
- $this->assertEquals('Grandchild class', $doc['description']);
+ $this->assertEquals('Grandchild class for Test, with a 2-line description!', $doc['description']);
- $expectedComment = 'This is an extended description.
+ $expectedComment = 'This is an extended comment.
-There is a line break in this description.
+ There is a line break in this comment.
This is the base class.';
$doc = ReflectionUtils::getCodeDocs($reflection->getProperty('foo'), 'Property');
$this->assertEquals('This is the foo property.', $doc['description']);
- $this->assertEquals("In the child class, foo has been barred.\n\nIn general, you can do nothing with it.", $doc['comment']);
+ $this->assertEquals("In the child class, foo has been barred.\n\n - In general, you can do nothing with it.", $doc['comment']);
}
public function docBlockExamples() {