diff --git a/App/Bootstrap/BaseServicesBootStrap.php b/App/Bootstrap/BaseServicesBootStrap.php index de9ee88..b4173bc 100644 --- a/App/Bootstrap/BaseServicesBootStrap.php +++ b/App/Bootstrap/BaseServicesBootStrap.php @@ -2,7 +2,7 @@ namespace App\Bootstrap; -use App\CInterface\BootstrapInterface; +use App\Interfaces\BootstrapInterface; use App\Constants\Services; use App\Library\Response; use OAuth2\GrantType\AuthorizationCode; diff --git a/App/Bootstrap/MiddlewareBootstrap.php b/App/Bootstrap/MiddlewareBootstrap.php index be70221..9e6107b 100644 --- a/App/Bootstrap/MiddlewareBootstrap.php +++ b/App/Bootstrap/MiddlewareBootstrap.php @@ -2,7 +2,7 @@ namespace App\Bootstrap; -use App\CInterface\BootstrapInterface; +use App\Interfaces\BootstrapInterface; use App\Middleware\RequestLoggerMiddleware; use App\Middleware\OAuthMiddleware; use Phalcon\Config; diff --git a/App/Bootstrap/RouteBootstrap.php b/App/Bootstrap/RouteBootstrap.php index 6839569..f96a905 100644 --- a/App/Bootstrap/RouteBootstrap.php +++ b/App/Bootstrap/RouteBootstrap.php @@ -2,12 +2,10 @@ namespace App\Bootstrap; -use App\Bootstrap\Bootstrap; -use App\CInterface\BootstrapInterface; +use App\Interfaces\BootstrapInterface; use Phalcon\Config; use Phalcon\Di\Injectable; use Phalcon\DiInterface; -use PhalconRest\Api; use Phalcon\Mvc\Micro\Collection as RouteHandler; /** diff --git a/App/Config/config.php b/App/Config/config.php index b5d1ba9..fa303c2 100644 --- a/App/Config/config.php +++ b/App/Config/config.php @@ -11,6 +11,12 @@ 'excluded_paths' => [ '/oauth' ] + ], + + 'requestLogger' => [ + 'excluded_paths' => [ + '/authentication' + ] ] ]); diff --git a/App/Config/config_common.php b/App/Config/config_common.php index a6e25ec..09c1673 100644 --- a/App/Config/config_common.php +++ b/App/Config/config_common.php @@ -5,7 +5,7 @@ 'modelsDir' => __DIR__ . '/../Model/', 'controllersDir' => __DIR__ . '/../Controller/', 'libsDir' => __DIR__ . '/../Library/', - 'interfacesDir' => __DIR__ . '/../CInterface/', + 'interfacesDir' => __DIR__ . '/../Interfaces/', 'pluginsDir' => __DIR__ . '/../plugins/', 'logsDir' => __DIR__ . '/../logs/', 'constantsDir' => __DIR__ . '/../Constants/', diff --git a/App/CInterface/BootstrapInterface.php b/App/Interfaces/BootstrapInterface.php similarity index 78% rename from App/CInterface/BootstrapInterface.php rename to App/Interfaces/BootstrapInterface.php index b9bd031..ff37725 100644 --- a/App/CInterface/BootstrapInterface.php +++ b/App/Interfaces/BootstrapInterface.php @@ -1,15 +1,16 @@ - * @package App + * @package App\Interfaces */ interface BootstrapInterface { diff --git a/App/CInterface/JSend.php b/App/Interfaces/JSend.php similarity index 92% rename from App/CInterface/JSend.php rename to App/Interfaces/JSend.php index 5a9a4f2..0027baa 100644 --- a/App/CInterface/JSend.php +++ b/App/Interfaces/JSend.php @@ -1,10 +1,11 @@ + * @package App\Interfaces */ interface JSend { @@ -31,4 +32,3 @@ public function sendSuccess($data); */ public function sendFail($data, $http_status_code = 500); } - diff --git a/App/Interfaces/Transformable.php b/App/Interfaces/Transformable.php new file mode 100644 index 0000000..3fd7d58 --- /dev/null +++ b/App/Interfaces/Transformable.php @@ -0,0 +1,17 @@ + + * @package App\Interfaces + */ +interface Transformable +{ + /** + * @author Adeyemi Olaoye + * @return mixed + */ + public function getModelsToLoad(); +} diff --git a/App/Library/Batch.php b/App/Library/Batch.php new file mode 100644 index 0000000..1ec48e6 --- /dev/null +++ b/App/Library/Batch.php @@ -0,0 +1,174 @@ +columns = ['score', 'name']; + * $batch->data = [ + * [1, 'john'], + * [4, 'fred'], + * [1, 'mickey'], + * ]; + * $batch->insert(); + * + */ +class Batch +{ + /** @var string */ + public $table = null; + + /** @var array */ + public $rows = []; + + /** @var array */ + public $values = []; + + // -------------------------------------------------------------- + + public function __construct($table = false) + { + if ($table) { + $this->table = (string)$table; + } + + $di = Di::getDefault(); + $this->db = $di->get('db'); + + return $this; + } + + // -------------------------------------------------------------- + + /** + * Set the Rows + * + * @param array $rows + * + * @return object Batch + */ + public function setRows($rows) + { + $this->rows = $rows; + $this->rowsString = sprintf('`%s`', implode('`,`', $this->rows)); + + return $this; + } + + // -------------------------------------------------------------- + + /** + * Set the values + * + * @param $values array + * + * @return object Batch + */ + public function setValues($values) + { + if (!$this->rows) { + throw new \Exception('You must setRows() before setValues'); + } + $this->values = $values; + + $valueCount = count($values); + $fieldCount = count($this->rows); + + // Build the Placeholder String + $placeholders = []; + for ($i = 0; $i < $valueCount; $i++) { + $placeholders[] = '(' . rtrim(str_repeat('?,', $fieldCount), ',') . ')'; + } + $this->bindString = implode(',', $placeholders); + + // Build the Flat Value Array + $valueList = []; + foreach ($values as $value) { + if (is_array($value)) { + foreach ($value as $v) { + $valueList[] = $v; + } + } else { + $valueList[] = $values; + } + } + $this->valuesFlattened = $valueList; + unset($valueList); + + return $this; + } + + // -------------------------------------------------------------- + + /** + * Insert into the Database + * + * @param boolean $ignore Use an INSERT IGNORE (Default: false) + * + * @return bool | int + */ + public function insert($ignore = false) + { + $this->validate(); + + // Optional ignore string + if ($ignore) { + $insertString = "INSERT IGNORE INTO `%s` (%s) VALUES %s"; + } else { + $insertString = "INSERT INTO `%s` (%s) VALUES %s"; + } + + $query = sprintf( + $insertString, + $this->table, + $this->rowsString, + $this->bindString + ); + + try { + $this->db->execute($query, $this->valuesFlattened); + return $this->db->affectedRows() > 0; + } catch (PDOException $ex) { + Di::getDefault()->get(Services::LOGGER)->error( + 'Could not perform bulk insert ' . $ex->getMessage() . ' TRACE: ' . $ex->getTraceAsString() + ); + throw new BatchInsertException($ex->getMessage()); + } + } + + // -------------------------------------------------------------- + + /** + * Validates the data before calling SQL + * + * @return void + */ + private function validate() + { + if (!$this->table) { + throw new \Exception('Batch Table must be defined'); + } + + $requiredCount = count($this->rows); + + if ($requiredCount == 0) { + throw new \Exception('Batch Rows cannot be empty'); + } + + foreach ($this->values as $value) { + if (count($value) !== $requiredCount) { + throw new \Exception('Batch Values must match the same column count of ' . $requiredCount); + } + } + } +} diff --git a/App/Library/HttpClient.php b/App/Library/HttpClient.php new file mode 100644 index 0000000..6d05a9b --- /dev/null +++ b/App/Library/HttpClient.php @@ -0,0 +1,75 @@ + + * @package App\Library + */ +class HttpClient +{ + protected $provider; + + public function __construct($timeout = null) + { + $this->provider = Request::getProvider(); + if ($this->provider instanceof Curl) { + if (!is_null($timeout)) { + $this->provider->setTimeout(intval($timeout)); + } + $this->provider->setOption(CURLOPT_SSL_VERIFYPEER, false); + $this->provider->setOption(CURLOPT_SSL_VERIFYHOST, false); + } + } + + public function setHeader($key, $value) + { + $this->provider->header->set($key, $value); + } + + public function get($url, $params = []) + { + $url = $url . '?' . http_build_query($params); + return $this->provider->get($url); + } + + + public function post($url, $data) + { + return $this->provider->post($url, $data); + } + + public function put($url, $data) + { + return $this->provider->put($url, $data); + } + + public function delete($url) + { + return $this->provider->delete($url); + } + + /** + * @return \Phalcon\Http\Client\Provider\Curl|\Phalcon\Http\Client\Provider\Stream + */ + public function getProvider() + { + return $this->provider; + } + + /** + * @author Adeyemi Olaoye + * @param $response ClientResponse + * @param int $successCode + * @return bool + */ + public static function isSuccessful($response, $successCode = 200) + { + return $response->header->statusCode === $successCode; + } +} diff --git a/App/Library/Logger.php b/App/Library/Logger.php new file mode 100644 index 0000000..eb6ece1 --- /dev/null +++ b/App/Library/Logger.php @@ -0,0 +1,66 @@ + + * @package App\Library + */ +class Logger extends File +{ + protected $debugEnabled; + + public function __construct($name, $options = null) + { + $this->debugEnabled = Di::getDefault()->get(Services::CONFIG)->debug; + parent::__construct($name, $options); + } + + public function debug($message, array $context = null) + { + if (!$this->debugEnabled) { + return; + } + parent::debug($message, $context); + } + + public function debugSoapServiceResponse($response) + { + $this->debug('Response Raw Data: ' . var_export($response, true)); + + if (method_exists($response, 'getResponseData')) { + $this->debug('Response Data: ' . $response->getResponseData()); + } + + if (method_exists($response, 'getResponseCode')) { + $this->debug('Response Code: ' . $response->getResponseCode()); + } + } + + public function debugSoapService(SoapClient $service) + { + $this->debug('Soap Request Headers: ' . $service->__getLastRequestHeaders()); + $this->debug('Soap Request: ' . $service->__getLastRequest()); + $this->debug('Soap Response Headers: ' . $service->__getLastResponseHeaders()); + $this->debug('Soap Response: ' . $service->__getLastResponse()); + } + + public function debugHttpServiceRequest(BaseGateway $service, array $requestData, $url) + { + $this->debug('Sending Request to ' . get_class($service) . ':' . json_encode($requestData) . + ' URL: ' . $service->buildURL($url)); + } + + public function debugHttpServiceResponse(BaseGateway $service, HttpResponse $response) + { + $this->debug('Service Response ' . get_class($service) . ':' . var_export($response, true)); + } +} diff --git a/App/Library/PaginationAdapter.php b/App/Library/PaginationAdapter.php new file mode 100644 index 0000000..a12b5d4 --- /dev/null +++ b/App/Library/PaginationAdapter.php @@ -0,0 +1,52 @@ + + * @package App\Library + */ +class PaginationAdapter implements AdapterInterface +{ + /** @var QueryBuilder $builder */ + protected $builder; + + /** @var \stdClass $paginateObject */ + protected $paginateObject; + + public function __construct(QueryBuilder $queryBuilder = null, \stdClass $paginateObject = null) + { + if (is_null($paginateObject)) { + $paginateObject = $queryBuilder->getPaginate(); + } + $this->paginateObject = $paginateObject; + $this->builder = $queryBuilder; + } + + /** + * Returns the number of results. + * + * @return integer The number of results. + */ + public function getNbResults() + { + return $this->paginateObject->total_items; + } + + /** + * Returns an slice of the results. + * + * @param integer $offset The offset. + * @param integer $length The length. + * + * @return array|\Traversable The slice. + */ + public function getSlice($offset, $length) + { + return $this->paginateObject->items; + } +} diff --git a/App/Library/Response.php b/App/Library/Response.php index 00a22eb..038d012 100644 --- a/App/Library/Response.php +++ b/App/Library/Response.php @@ -2,7 +2,7 @@ namespace App\Library; -use App\CInterface\JSend; +use App\Interfaces\JSend; use App\Constants\HttpStatusCodes; use App\Constants\ResponseMessages; use Phalcon\Http\Response as PhalconResponse; diff --git a/App/Library/Util.php b/App/Library/Util.php index 4a6398a..5365ae8 100644 --- a/App/Library/Util.php +++ b/App/Library/Util.php @@ -2,6 +2,12 @@ namespace App\Library; +use App\Constants\Services; +use Handlebars\Handlebars; +use Phalcon\Di; +use Phalcon\Mailer\Manager as MailManager; +use stdClass; + /** * Class Util * @author Adeyemi Olaoye @@ -73,4 +79,158 @@ public static function getValue($array, $key, $default = null) return $default; } } -} \ No newline at end of file + + /** + * Reads a CSV file + * @credit http://www.codedevelopr.com/articles/reading-csv-files-into-php-array/ + * @param $csvFile + * @return array + */ + public static function readCSV($csvFile) + { + ini_set('auto_detect_line_endings', true); + $file_handle = fopen($csvFile, 'r'); + while (!feof($file_handle)) { + $line_of_text[] = fgetcsv($file_handle, 1024); + } + fclose($file_handle); + return $line_of_text; + } + + /** + * Get unique array or column elements + * @author Adeyemi Olaoye + * @param array $array + * @param $elementKey + * @return array + */ + public static function getUniqueColumnElements(array $array, $elementKey) + { + $uniqueElements = []; + foreach ($array as $key => $value) { + if (is_array($value) && isset($value[$elementKey])) { + $value = $value[$elementKey]; + } elseif (is_object($value) && property_exists($value, $elementKey)) { + $value = $value->{$elementKey}; + } else { + continue; + } + if (!in_array($value, $uniqueElements, true)) { + $uniqueElements[] = $value; + } + } + return $uniqueElements; + } + + /** + * @author Adeyemi Olaoye + * @param array $array + * @param string $prefix + * @param bool $mixed + * @return array + */ + public static function prependToArrayKeys(array $array, $prefix, $mixed = true) + { + $result = []; + foreach ($array as $key => $value) { + if (!is_string($key)) { + $result[] = $prefix . $value; + continue; + } + + $result[$prefix . $key] = $value; + } + + return $result; + } + + /** + * Send message + * @author Adeyemi Olaoye + * @param $template + * @param $subject + * @param $to + * @param array $params + * @param array $extras + * @return bool + */ + public static function send($template, $subject, $to, $params = [], $extras = []) + { + /** @var MailManager $mailer */ + $mailer = Di::getDefault()->get(Services::MAILER); + + $mailMessage = $mailer->createMessage() + ->to((array)$to) + ->subject(Util::getActualMessage($subject, $params)) + ->content(Util::getActualMessage($template, $params)) + ->cc((array)Util::getValue($extras, 'cc', new stdClass())) + ->bcc((array)Util::getValue($extras, 'bcc', new stdClass())) + ->contentType('text/html'); + + if (Util::getValue($extras, 'attachment')) { + $mailMessage->attachmentData( + base64_decode(Util::getValue($extras, 'attachment.content', '')), + Util::getValue($extras, 'attachment.name', ''), + ['mime' => Util::getValue($extras, 'attachment.mime', '')] + ); + } + + if (Util::getValue($extras, 'from')) { + $mailMessage->from((array)Util::getValue($extras, 'from')); + } + + try { + return $mailMessage->send() > 0; + } catch (\Exception $ex) { + return false; + } + } + + /** + * @author Adeyemi Olaoye + * @param $message + * @param array $params + * @return string + */ + public static function getActualMessage($message, array $params) + { + $engine = new Handlebars(); + return $engine->render($message, $params); + } + + /** + * @author Adeyemi Olaoye + * @param $imageData + * @return int|null|string + * @credits http://stackoverflow.com/a/35996452/1215010 + */ + public static function getBase64ImageMimeType($imageData) + { + $imageData = base64_decode($imageData); + $f = finfo_open(); + $mimeType = finfo_buffer($f, $imageData, FILEINFO_MIME_TYPE); + return ($mimeType ?: null); + } + + /** + * @author Adeyemi Olaoye + * @param $mixed + * @return array|string + * @credits http://stackoverflow.com/questions/10199017/how-to-solve-json-error-utf8-error-in-php-json-decode + */ + public static function utf8ize($mixed) + { + if (is_array($mixed)) { + foreach ($mixed as $key => $value) { + $mixed[$key] = self::utf8ize($value); + } + } elseif (is_object($mixed)) { + foreach ($mixed as $key => $value) { + $mixed->$key = self::utf8ize($value); + } + } elseif (is_string($mixed)) { + return utf8_encode($mixed); + } + return $mixed; + } +} diff --git a/App/Middleware/BaseMiddleware.php b/App/Middleware/BaseMiddleware.php new file mode 100644 index 0000000..e37e95d --- /dev/null +++ b/App/Middleware/BaseMiddleware.php @@ -0,0 +1,44 @@ + + * @package App\Middleware + */ +abstract class BaseMiddleware extends Plugin implements MiddlewareInterface +{ + /** + * check if path is excluded from middleware + * @author Adeyemi Olaoye + * @param array $excludedPaths + * @return bool + */ + public function isExcludedPath($excludedPaths) + { + $request = new HttpRequest(); + $basePath = $request->getQuery('_url'); + + if (is_null($excludedPaths)) { + $excludedPaths = []; + } elseif ($excludedPaths instanceof Config) { + $excludedPaths = $excludedPaths->toArray(); + } else { + $excludedPaths = (array)$excludedPaths; + } + + foreach ($excludedPaths as $key => $value) { + if (substr($basePath, 0, strlen($value)) == $value && $value != '/') { + return true; + } + } + + return false; + } +} diff --git a/App/Middleware/OAuthMiddleware.php b/App/Middleware/OAuthMiddleware.php index fd9c900..e345630 100644 --- a/App/Middleware/OAuthMiddleware.php +++ b/App/Middleware/OAuthMiddleware.php @@ -8,8 +8,6 @@ use OAuth2\Request; use OAuth2\Server; use Phalcon\Mvc\Micro; -use Phalcon\Mvc\Micro\MiddlewareInterface; -use PhalconRest\Mvc\Plugin; use Phalcon\Http\Request as HttpRequest; /** @@ -17,37 +15,11 @@ * @author Adeyemi Olaoye * @package App\Middleware */ -class OAuthMiddleware extends Plugin implements MiddlewareInterface +class OAuthMiddleware extends BaseMiddleware { - /** - * check if path is excluded from authentication - * @author Adeyemi Olaoye - * @return bool - */ - private function isExcludedPath() - { - $request = new HttpRequest(); - $basePath = $request->getQuery('_url'); - $excludedPaths = $this->getDI()->get(Services::CONFIG)->oauth->excluded_paths; - - if (is_null($excludedPaths)) { - $excludedPaths = []; - } else { - $excludedPaths = $excludedPaths->toArray(); - } - - foreach ($excludedPaths as $key => $value) { - if (substr($basePath, 0, strlen($value)) == $value && $value != '/') { - return true; - } - } - - return false; - } - public function call(Micro $application) { - if ($this->isExcludedPath()) { + if ($this->isExcludedPath($this->getDI()->get(Services::CONFIG)->oauth->excluded_paths)) { return true; } diff --git a/App/Middleware/RequestLoggerMiddleware.php b/App/Middleware/RequestLoggerMiddleware.php index cdffbfb..230895e 100644 --- a/App/Middleware/RequestLoggerMiddleware.php +++ b/App/Middleware/RequestLoggerMiddleware.php @@ -6,25 +6,27 @@ use Phalcon\Logger; use Phalcon\Logger\Adapter\File; use Phalcon\Mvc\Micro; -use Phalcon\Mvc\Micro\MiddlewareInterface; -use PhalconRest\Mvc\Plugin; /** * Class LoggerMiddleware * @author Adeyemi Olaoye * @package App\Middleware */ -class RequestLoggerMiddleware extends Plugin implements MiddlewareInterface +class RequestLoggerMiddleware extends BaseMiddleware { public function beforeExecuteRoute() { + if ($this->isExcludedPath($this->getDI()->get(Services::CONFIG)->requestLogger->excluded_paths)) { + return true; + } + /** @var \Phalcon\Http\Request $request */ $request = $this->getDI()->get(Services::REQUEST); - + $config = $this->getDI()->get(Services::CONFIG); - + $logger = new File($config->application->logsDir . "requests.log"); - + $logger->log('Request URL:' . $request->getURI(), Logger::INFO); if ($request->isPost() || $request->isPut()) { $rawBody = $request->getRawBody(); diff --git a/App/Services/BaseGateway.php b/App/Services/BaseGateway.php new file mode 100644 index 0000000..e6dc7aa --- /dev/null +++ b/App/Services/BaseGateway.php @@ -0,0 +1,80 @@ + + * @package App\Service + */ +abstract class BaseGateway +{ + /** @var $logger Logger */ + public $logger; + protected $httpClient; + protected $baseUrl; + protected $lastError; + + public function __construct($baseUrl, $timeout) + { + $this->httpClient = new HttpClient($timeout); + $this->baseUrl = $baseUrl; + $this->logger = Di::getDefault()->get(Services::LOGGER); + } + + /** + * Build URL + * @author Adeyemi Olaoye + * @param $url + * @return string + */ + public function buildURL($url) + { + return $this->baseUrl . $url; + } + + /** + * @author Adeyemi Olaoye + * @param $response HttpResponse + * @param bool $default + * @param bool $convertToArray + * @param int $successCode + * @return mixed + */ + public function decodeJsonResponse($response, $default = false, $convertToArray = false, $successCode = 200) + { + if (!($response instanceof HttpResponse)) { + $this->logger->error(get_called_class() . ' Service Error: ' . $response); + $this->lastError = $response; + return $default; + } + + if (!HttpClient::isSuccessful($response, $successCode)) { + $this->logger->error(get_called_class() . ' Service Error: ' . $response->body); + $this->lastError = $response->body; + return $default; + } + + $decodedResponse = json_decode($response->body, $convertToArray); + if (!$decodedResponse) { + return $default; + } + + return $decodedResponse; + } + + /** + * @author Adeyemi Olaoye + * @return mixed + */ + public function getLastError() + { + return ($this->lastError) ?: 'Unknown Error'; + } +} diff --git a/App/Transformers/ModelTransformer.php b/App/Transformers/ModelTransformer.php new file mode 100644 index 0000000..addf5f3 --- /dev/null +++ b/App/Transformers/ModelTransformer.php @@ -0,0 +1,19 @@ + + * @package App\Transformer + */ +class ModelTransformer extends Transformer +{ + public function transform(Model $model) + { + return $model->toArray(); + } +} diff --git a/App/Validations/CommonValidations.php b/App/Validations/CommonValidations.php new file mode 100644 index 0000000..1bce99f --- /dev/null +++ b/App/Validations/CommonValidations.php @@ -0,0 +1,215 @@ + + * @package App\Validation + */ +trait CommonValidations +{ + /** + * @author Adeyemi Olaoye + * @param $field + * @param bool $cancelOnFail + */ + public function addIsObjectValidation($field, $cancelOnFail = true) + { + if (!is_null($this->getValue($field))) { + $this->add($field, new InlineValidator([ + 'function' => function () use ($field) { + return is_object($this->getValue($field)); + }, + 'message' => sprintf(ValidationMessages::PARAMETER_MUST_BE_AN_OBJECT, $field), + 'cancelOnFail' => $cancelOnFail + ])); + } + } + + /** + * @author Adeyemi Olaoye + * @param $field + * @param bool $cancelOnFail + */ + public function addIsArrayValidation($field, $cancelOnFail = true) + { + if (!is_null($this->getValue($field))) { + $this->add($field, new InlineValidator([ + 'function' => function () use ($field) { + return is_array($this->getValue($field)); + }, + 'message' => sprintf(ValidationMessages::PARAMETER_MUST_BE_AN_ARRAY, $field), + 'cancelOnFail' => $cancelOnFail + ])); + } + } + + /** + * Validate bulk model fields + * @author Adeyemi Olaoye + * @param $field + * @param $modelClass + * @param string $bulkField + */ + public function validateBulkModelField($field, $modelClass, $bulkField) + { + $this->validateValidity( + $field, + function ($uniqueAdvertIds) use ($modelClass) { + $uniqueAdverts = $modelClass::query()->inWhere('id', $uniqueAdvertIds)->execute(); + return count($uniqueAdvertIds) == $uniqueAdverts->count(); + }, + $bulkField + ); + } + + /** + * Validate validity of field + * @author Adeyemi Olaoye + * @param $field + * @param $validateFunction + * @param string $bulkField + */ + public function validateValidity($field, $validateFunction, $bulkField) + { + if (is_array($this->getValue($bulkField))) { + $uniqueElements = Util::getUniqueColumnElements($this->getValue($bulkField), $field); + if (!empty($uniqueElements)) { + $this->add($bulkField, new InlineValidator([ + 'function' => function () use ($uniqueElements, $validateFunction) { + return call_user_func($validateFunction, $uniqueElements); + }, + 'message' => sprintf(ValidationMessages::PARAMETER_CONTAINS_INVALID_FIELD, $field), + 'cancelOnFail' => true + ])); + } + } + } + + /** + * @author Adeyemi Olaoye + * @param $field + * @param $requiredElements + */ + public function validateArrayElementsHasFields($field, array $requiredElements) + { + $fieldValue = $this->getValue($field); + if (!is_array($fieldValue)) { + return; + } + + $this->add($field, new InlineValidator([ + 'function' => function () use ($fieldValue, $requiredElements) { + foreach ($fieldValue as $element) { + foreach ($requiredElements as $requiredElement) { + $validation = new RequestValidation($element); + $validation->add($requiredElement, new PresenceOf(['allowEmpty' => false])); + if (!$validation->validate()) { + return false; + } + } + } + return true; + }, + 'message' => sprintf(ValidationMessages::INCORRECT_ELEMENT_STRUCTURE, $field), + 'cancelOnFail' => true + ])); + } + + /** + * @author Adeyemi Olaoye + * @param $field + * @param $requiredElements + */ + public function validateArrayElements($field, array $requiredElements) + { + $fieldValue = $this->getValue($field); + if (!is_array($fieldValue)) { + return; + } + + $this->add($field, new InlineValidator([ + 'function' => function () use ($fieldValue, $requiredElements) { + foreach ($fieldValue as $element) { + foreach ($requiredElements as $requiredElement => $validations) { + $requestValidation = new RequestValidation($element); + foreach ($validations as $validation) { + $requestValidation->add($requiredElement, $validation); + } + if (!$requestValidation->validate()) { + Di::getDefault()->get(Services::LOGGER)->debug($requestValidation->getMessages()); + return false; + } + } + } + return true; + }, + 'message' => sprintf(ValidationMessages::INCORRECT_ELEMENT_STRUCTURE, $field), + 'cancelOnFail' => true + ])); + } + + /** + * @author Adeyemi Olaoye + * @param $field + */ + public function isNotEmpty($field) + { + $this->add($field, new InlineValidator([ + 'function' => function () use ($field) { + $value = $this->getValue($field); + if (is_array($value) && $value) { + return true; + } + + if (is_object($value)) { + $value = (array)$value; + return ($value); + } + + return !empty($this->getValue($field)); + } + ])); + } + + /** + * @author Adeyemi Olaoye + * @param $field + * @param bool $cancelOnFail + */ + public function validateBooleanValue($field, $cancelOnFail = true) + { + $this->add($field, new InlineValidator([ + 'function' => function () use ($field) { + return in_array($this->getValue($field), [0, 1, '0', '1', true, false], true); + }, + 'message' => sprintf(ValidationMessages::PARAMETER_MUST_BE_BOOLEAN, $field), + 'cancelOnFail' => $cancelOnFail + ])); + } + + /** + * @author Adeyemi Olaoye + * @param $field + * @param $model + * @param string $column + */ + public function validateDataField($field, $model, $column = 'key') + { + $this->add($field, new Model([ + 'model' => $model, + 'conditions' => $column.' = :key:', + 'bind' => ['key' => $this->getValue($field)], + 'message' => sprintf(ValidationMessages::INVALID_PARAMETER_SUPPLIED, $field) + ])); + } +} diff --git a/App/Validations/ValidationConstants.php b/App/Validations/ValidationConstants.php new file mode 100644 index 0000000..795b7c0 --- /dev/null +++ b/App/Validations/ValidationConstants.php @@ -0,0 +1,13 @@ + + * @package App\Validation + */ +class ValidationConstants +{ + +} diff --git a/App/Validations/ValidationMessages.php b/App/Validations/ValidationMessages.php new file mode 100644 index 0000000..55ae83f --- /dev/null +++ b/App/Validations/ValidationMessages.php @@ -0,0 +1,20 @@ + + * @package App\Validation + */ +class ValidationMessages +{ + const INVALID_PARAMETER_SUPPLIED = 'Invalid %s supplied'; + const PARAMETER_NOT_FOUND = '%s not found'; + const PARAMETER_MUST_BE_AN_OBJECT = '%s must be an object'; + const PARAMETER_MUST_BE_AN_ARRAY = '%s must be an array'; + const PARAMETER_CONTAINS_INVALID_FIELD = '%s contains invalid field'; + const PARAMETER_MUST_BE_BOOLEAN = '%s must be boolean'; + const PARAMETER_IS_REQUIRED = '%s is required'; + const MERCHANT_ALREADY_HAS_MEETING_SCHEDULED_FOR_THIS_TIME = 'Merchant already has meeting scheduled for this time'; +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 19b67eb..0ceed78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,23 @@ All Notable changes to `phalcon-base-project` will be documented in this file. Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles. -## 1.0.0 +## [1.0.0] - 2016-10-27 ### Added -- Added Base Project Features *2016-10-27* +- Added Base Project Features + +## [1.1.0] - 2017-05-24 + +### Added + - Added BaseMiddleWare Class to handle common middleware actions + - Added BaseGateway + - Added Custom Logger + - Added ModelTransformer + - Added CommonValidations + - Added BatchInsert, HttpClient and PaginatorAdapter library classes + - Added more Util functions + + ### Changed + - Changed Interfaces namespace + - Added more suggestions to composer.json + - Updated README to reflect nginx setup diff --git a/README.md b/README.md index a68411c..e64bc93 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,32 @@ Sample Virtual Host Config for Apache ``` +Sample Server Block for nginx +``` +server { + listen 80; + server_name test.phalconbaseproject.com; + root /public; + index index.php; + charset utf-8; + access_log /test.phalconbaseproject.com.access.log; + error_log /test.phalconbaseproject.com.error.log; + + location / { + try_files $uri $uri/ /index.php?_url=$uri&$args; + } + + + location ~ \.php$ { + fastcgi_pass unix:; + fastcgi_index index.php; + fastcgi_param APPLICATION_ENV test; + include /fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name; + } +} +``` + ## Environment Variables Make a copy of `.env.sample` to `.env` in the env directory and replace values as appropriate. diff --git a/composer.json b/composer.json index 7877ff2..82411d5 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,6 @@ "project template", "improved" ], - "version": "1.0.0", "type": "project", "license": "MIT", "minimum-stability": "stable", @@ -47,7 +46,11 @@ ], "suggest": { "pagerfanta/pagerfanta": "Install PagerFanta to use Pagination", - "league/fractal": "Install Fractal to use Transformers" + "league/fractal": "Install Fractal to use Transformers", + "wsdl2phpgenerator/wsdl2phpgenerator": "Install WSDL2PHP generator to generate classes from WSDL", + "besimple/soap-client": "Install SOAP Client for connecting with SOAP services", + "xamin/handlebars.php": "Install Handlebars for fluent templating", + "mcustiel/phiremock-codeception-extension": "Install phiremock for mocking" } } diff --git a/tests/_support/_generated/ApiTesterActions.php b/tests/_support/_generated/ApiTesterActions.php index b243fa7..9264aa5 100644 --- a/tests/_support/_generated/ApiTesterActions.php +++ b/tests/_support/_generated/ApiTesterActions.php @@ -1,4 +1,4 @@ -getScenario()->runStep(new \Codeception\Step\Action('grabPageSource', func_get_args())); + } + + /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -2430,13 +2445,35 @@ public function amBearerAuthenticated($accessToken) { /** * [!] Method is generated. Documentation taken from corresponding module. * - * Sends a POST request to given uri. + * Sends a POST request to given uri. Parameters and files can be provided separately. * - * Parameters and files (as array of filenames) can be provided. + * Example: + * ```php + * sendPOST('/message', ['subject' => 'Read this!', 'to' => 'johndoe@example.com']); + * //simple upload method + * $I->sendPOST('/message/24', ['inline' => 0], ['attachmentFile' => codecept_data_dir('sample_file.pdf')]); + * //uploading a file with a custom name and mime-type. This is also useful to simulate upload errors. + * $I->sendPOST('/message/24', ['inline' => 0], [ + * 'attachmentFile' => [ + * 'name' => 'document.pdf', + * 'type' => 'application/pdf', + * 'error' => UPLOAD_ERR_OK, + * 'size' => filesize(codecept_data_dir('sample_file.pdf')), + * 'tmp_name' => codecept_data_dir('sample_file.pdf') + * ] + * ]); + * ``` * * @param $url * @param array|\JsonSerializable $params - * @param array $files + * @param array $files A list of filenames or "mocks" of $_FILES (each entry being an array with the following + * keys: name, type, error, size, tmp_name (pointing to the real file path). Each key works + * as the "name" attribute of a file input field. + * + * @see http://php.net/manual/en/features.file-upload.post-method.php + * @see codecept_data_dir() * @part json * @part xml * @see \Codeception\Module\REST::sendPOST() @@ -3765,6 +3802,17 @@ public function startFollowingRedirects() { } + /** + * [!] Method is generated. Documentation taken from corresponding module. + * + * + * @see \Codeception\Module\Db::isPopulated() + */ + public function isPopulated() { + return $this->getScenario()->runStep(new \Codeception\Step\Action('isPopulated', func_get_args())); + } + + /** * [!] Method is generated. Documentation taken from corresponding module. * @@ -3956,14 +4004,29 @@ public function grabNumRecords($table, $criteria = null) { /** * [!] Method is generated. Documentation taken from corresponding module. * - * Checks that two variables are equal. + * Checks that two variables are equal. If you're comparing floating-point values, + * you can specify the optional "delta" parameter which dictates how great of a precision + * error are you willing to tolerate in order to consider the two values equal. + * + * Regular example: + * ```php + * assertEquals($element->getChildrenCount(), 5); + * ``` + * + * Floating-point example: + * ```php + * assertEquals($calculator->add(0.1, 0.2), 0.3, 'Calculator should add the two numbers correctly.', 0.01); + * ``` * * @param $expected * @param $actual * @param string $message + * @param float $delta * @see \Codeception\Module\Asserts::assertEquals() */ - public function assertEquals($expected, $actual, $message = null) { + public function assertEquals($expected, $actual, $message = null, $delta = null) { return $this->getScenario()->runStep(new \Codeception\Step\Action('assertEquals', func_get_args())); } @@ -3971,14 +4034,29 @@ public function assertEquals($expected, $actual, $message = null) { /** * [!] Method is generated. Documentation taken from corresponding module. * - * Checks that two variables are not equal + * Checks that two variables are not equal. If you're comparing floating-point values, + * you can specify the optional "delta" parameter which dictates how great of a precision + * error are you willing to tolerate in order to consider the two values not equal. + * + * Regular example: + * ```php + * assertNotEquals($element->getChildrenCount(), 0); + * ``` + * + * Floating-point example: + * ```php + * assertNotEquals($calculator->add(0.1, 0.2), 0.4, 'Calculator should add the two numbers correctly.', 0.01); + * ``` * * @param $expected * @param $actual * @param string $message + * @param float $delta * @see \Codeception\Module\Asserts::assertNotEquals() */ - public function assertNotEquals($expected, $actual, $message = null) { + public function assertNotEquals($expected, $actual, $message = null, $delta = null) { return $this->getScenario()->runStep(new \Codeception\Step\Action('assertNotEquals', func_get_args())); } @@ -3991,7 +4069,6 @@ public function assertNotEquals($expected, $actual, $message = null) { * @param $expected * @param $actual * @param string $message - * @return mixed|void * @see \Codeception\Module\Asserts::assertSame() */ public function assertSame($expected, $actual, $message = null) {