Jump to content

NotionCommotion

Members
  • Content Count

    1,740
  • Joined

  • Last visited

  • Days Won

    8

Everything posted by NotionCommotion

  1. MySQL cannot store Boolean values and thus I use 0/1. Is there much benefit to cast them as Boolean immediately after querying the database, do whatever PHP processing is required, and then convert them back to 0/1 before writing to the database? One benefit is I can type declare my arguments, but I am debating whether it is worth it. Thanks
  2. NotionCommotion

    Recommend using true/false for Boolean values?

    Yes they are but 1/0 will not work with the following: public function foo(bool $status):bool;
  3. NotionCommotion

    Recommend using true/false for Boolean values?

    Ah yes, I do recall that now. I've never really used bindValue, so have been kind of stuck. What about my original question? Is what I am asking clear? On a related note, do you know if it is possible to make PDO output data as boolean? I searched and it appears not to be possible.
  4. NotionCommotion

    Recommend using true/false for Boolean values?

    Thank you. I was unaware. Are you saying one can do $pdo->prepare($sql)->execute([123, true, false]); ? I am not asking from a MySQL prospective but a PHP prospective. In the past, I never explicitly declared variable argument and returned types, and found that it got me in a lot of trouble. I am asking whether it is good practice to perform boolval($mysqlOutput) or (bool) $mysqlOutput on data received from MySQL before processing the data with PHP.
  5. Originally, I would get both, and unfortunately would inconsistently use both. Then, I wanted more consistently, so configured php.ini to only return objects as I felt accessing them was more concise. And then later, I found myself needing arrays more often, and initially would just typecast them to arrays but eventually my standard was to set the PDO's fetch style to an array. And now I find myself almost always explicitly requesting arrays and occasionally requesting columns or named values. Do you configure ATTR_DEFAULT_FETCH_MODE, and if so to what? PS. Anyone use FETCH_CLASS, FETCH_INTO or FETCH_NUM? If so, what would be a good use case?
  6. NotionCommotion

    Returning PDO query by default as array or objects

    Good point. As seen again, using the default settings are usually the way to go unless one has a good reason to do differently. Related, do you explicitly set any default values such as PDO:FETCH_BOTH? The reason to do so is "if" PHP changes, the application will not break, and it clearly communicates which mode is being used. But cluttering script with a bunch of redundant settings will cause more trouble than it is worth. Never mind, answered my question. There is a much about fetch modes which I did not know, and I recently stumbled upon https://phpdelusions.net/pdo/fetch_modes#FETCH_GROUP
  7. NotionCommotion

    Get specific MySQL/MariaDB error info

    Thanks Barand, I stand corrected.
  8. NotionCommotion

    Get specific MySQL/MariaDB error info

    I wish to respond specifically to PDO errors. $e->getMessage() shows something like the following: SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails... SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry ... I suppose I can just look for 1452 and 1062, but thought using http://php.net/manual/en/pdo.errorcode.php would be better, but it is empty. I am using Server version: 10.2.18-MariaDB MariaDB Server. Any suggestions? Thanks try{ $this->pdo->prepare($sql)->execute([$value, $id, $this->accountsId]); } catch(\PDOException $e) { syslog(LOG_ERR, json_encode($this->pdo->errorCode())); syslog(LOG_ERR, json_encode($this->pdo->errorInfo())); } "00000" ["00000",null,null]
  9. NotionCommotion

    Get specific MySQL/MariaDB error info

    I don't think this will help determine whether a duplicate or fk issue.
  10. NotionCommotion

    Get specific MySQL/MariaDB error info

    Thanks benanamen, I missed that part. Looks like I need to look into http://php.net/manual/en/pdostatement.errorcode.php
  11. NotionCommotion

    Get specific MySQL/MariaDB error info

    I have no issues with the clarity of the error message. Why does PDO::errorCode() and PDO::errorInfo() provide no content?
  12. NotionCommotion

    Returning PDO query by default as array or objects

    Ah, that's right, there are some good uses for FETCH_NUM. After more thought, FETCH_CLASS will come in handy, but by no means should be a default. I plan on makding FETCH_ASSOC my default as well. Thanks
  13. NotionCommotion

    Updating using name/value pairs

    I agree my dynamically dispatched methods had a lot to be desired in their original form. My new approach is definitely better as I don't need to use parent::update($prop, $value, $service); and have an object with methods. That being said, I may go with your recommended updateValueDatabase ($prop, $value, $service);. Also, not only are some of these approaches too complicated and obscure for anyone but the author, in my case they are too complicated and obscure for myself
  14. NotionCommotion

    Updating using name/value pairs

    The client provides name/value pairs which are used to update something and I am currently using switch statements to direct the action based on the provided name, however, it is becoming increasingly more complicated. Are there any standard approaches to doing so? I am thinking of doing the following. Any concerns? Thanks $c['fooService'] = function ($c) { return new Foo\FooService( new Foo\FooMapper(), new ApiConnector(), new Foo\FooNameValueUpdater(), ); }; $app->put('/foo/{id:[0-9]+}', function (Request $request, Response $response, $args) { return $this->fooResponder->update($response, $this->fooService->update($args['id'], $request->getParsedBody())); }); Class FooService extends BarService { public function __construct($mapper, $updater, $apiConnector) { $this->mapper = $mapper; $this->apiConnector = $apiConnector; $this->updater = $updater; } public function update($id, $params) { //$params=['name'=>'property1name', 'value'=>123] $this->updater->$params['name']($id, $params['value'], $this); } public function updateSomeStandardAction($id, $value, $propertyName) { //some code... $this->mapper->bla($id, $value, $propertyName); } public function updateSomeOtherStandardAction($id, $value, $propertyName) { //some code... $this->apiConnector->bla($id, $value, $propertyName); } } Class FooNameValueUpdater extends BarNameValueUpdater { public function property1name($id, $value, $service) { $this->updateSomeStandardAction($id, $value, $service, 'property1name'); } public function property2name($id, $value, $service) { $this->updateSomeStandardAction($id, $value, $service, 'property2name'); } public function property3name($id, $value, $service) { $this->updateSomeStandardAction($id, $value, $service, 'property3name'); } public function property4name($id, $value, $service) { $this->updateSomeStandardAction($id, $value, $service, 'property4name'); } protected function updateSomeStandardAction($id, $value, $service, $propertyName) { //some code... $service->updateSomeStandardAction($id, $value, $propertyName); } protected function updateSomeOtherStandardAction($id, $value, $service, $propertyName) { //some code... $service->updateSomeOtherStandardAction($id, $value, $propertyName); } }
  15. NotionCommotion

    Updating using name/value pairs

    I didn't post my existing code but just said it used a switch statement. The following method is located in the controller/service and does the name to method routing. I want to allow inheritance which makes it even uglier. Some of the endpoints need to initiate multiple processes so I sometimes return a single method and other times return an array of methods. My new approach is using a dedicated class for the routing which will throw an error if the method doesn't exist. The part I've never done on my new approach is have the service pass itself to the methods in the Updater method. Originally I was planning on just injecting the mapper when creating the Updater but the mapper doesn't contain all the needed functionality. protected function getUpdateMethods(string $prop) { switch ($prop) { case 'name': case 'virtualLansId': return 'updateValueDatabase'; case 'guid': return ['updateValueDatabase','disconnect']; case 'reconnectTimeout': case 'responseTimeout': case 'historyPackSize': return ['updateValueDatabase','updateClient']; default: return parent::getUpdateMethods($prop); } }
  16. NotionCommotion

    Updating using name/value pairs

    One originally thinks they all work the same way and then there is some new requirement and then another, and before you know it... I intended to show two of them as "updateSomeOtherStandardAction" but failed to do so. Did my proposed solution make any sense?
  17. NotionCommotion

    Where should validation be performed?

    Where should the validation take place? Several options include: In the Slim closure. i.e. $app->get('/', function(){/* validate before calling service */}) In the service. In the mapper. In the entity domain. I can easily create the standalone Validator class to validate scenarios such as whether a property is provided and whether it meets certain rules, however, other scenarios such as whether a record exists is closely linked to the mapper. Also, the entity does a good job confirming that the write properties are being provided. How important is it to locate validation at one place? Thanks <?php use \Psr\Http\Message\ServerRequestInterface as Request; use \Psr\Http\Message\ResponseInterface as Response; $c = new \Slim\Container(); $c['validator'] = function ($c) {}; $c['pdo'] = function ($c) {}; $c['resourceService'] = function ($c) { return new Resource\ResourceService( new Resource\ResourceMapper( $c['pdo'], $c['validator'] ), $c['validator'] ); }; $c['resourceResponder'] = function ($c) {}; $app = new \Slim\App($c); $app->get('/someResource', function (Request $request, Response $response) { return $this->resourceResponder->index($response, $this->resourceService->index($request->getQueryParams())); }); $app->get('/someResource/{id:[0-9]+}', function (Request $request, Response $response, $args) { return $this->resourceResponder->detail($response, $this->resourceService->read($args['id'])); }); $app->post('/someResource', function (Request $request, Response $response, $args) { return $this->resourceResponder->create($response, $this->resourceService->create($request->getParsedBody())); }); $app->put('/someResource/{id:[0-9]+}', function (Request $request, Response $response, $args) { return $this->resourceResponder->update($response, $this->resourceService->update($args['id'], $request->getParsedBody())); }); $app->delete('/someResource/{id:[0-9]+}', function (Request $request, Response $response, $args) { return $this->resourceResponder->delete($response, $this->resourceService->delete($args['id'])); }); $app->run(); class ResourceService { protected $mapper, $validator; public function __construct(Mapper $mapper, Validator $validator) { $this->mapper = $mapper; $this->validator = $validator; } public function index(array $params=[]):array { $index = $this->mapper->index($params); return $index; } public function read(int $id):Entity { $entity = $this->mapper->read($id); return $entity; } public function create(array $params):int { $entity=$this->mapper->create($params); $id=$this->mapper->save($entity); return $id; } public function update(int $id, array $params):int { $this->update->update($id, $params); return $id; } public function delete(int $id):null { $this->mapper->delete($id); } } class ResourceMapper { protected $pdo, $validator; public function __construct(\Pdo $pdo, Validator $validator) { $this->pdo = $pdo; $this->validator = $validator; } public function index(array $params=[]):array { //query DB and return an array of Resources } public function read(int $id):Entity { if(!$params=$this->queryDatabase($id)) { throw new \Exception("ID $id does not exist"); } return new Resource($params); } public function create(array $params):int { //Or should the service create the entity? return new ResourceEntity($params); } public function save(Entity $entity):int { //Save the data. What if a duplicate error? return $this->pdo->lastInsertId(); } public function update(int $id, array $params):null { //update database. What if id doesn't exist? } public function delete(int $id):null { //Delete from DB. What if id doesn't exist or foreign key constraint? } } class ResourceEntity { public function __construct(array $params, Validator $validator) { //As applicable } }
  18. I've always had an issue needing to write and maintain script to validate both client side and sever side so a while back I wrote a validation class whose primary purpose was: Accept in its constructor a JSON string which is mostly identical to the jQuery validation option except it also included a "sanitize" property. For instance: {"rules": {"account": {"minlength": 6}}, "messages": {"account": {"minlength": "Six numbers are required"}, "sanitize":{"account":int}}. Provide the correct jQuery validation option so a form can be validated client side. Sanitize the form data server side. Validate the form data server side. I recently looked at the code and it is a train wreck. I used a single class and had a bunch of protected functions where some would validate per a given rule and return the message upon error and others would sanitize. For non-typical rules required by the specific application, I would extend the class and add them. Some of these added rules required other resources such as an entities primary key, a PDO connection, etc. While inheritance worked fine to add simple rules, it did not do so for the rules which needed other resources. Also, even though I used "final" on my base class methods, I never liked this single class of many methods as I couldn't use the same name for a given rule and sanitize method (i.e. would rather use digit and digit instead of digit and int). I am thinking of changing it to something like the following. While this is definitely better, I question whether I should be using traditional classes to define my standard and custom rules. Any recommendations? Thanks class Validator { protected $options, $rules=[ "digit"=>function($value, $prop, $name){ return /*validate and return error string or null*/; }, "minlength"=>"ect, etc" ]; public function __construct(string $json, \Closure ...$customRules=[]) { $this->options=json_decode($json); $this->rules=array_merge($this->rules, $customRules); } } class Application { public function someMethod() { $pdo=$this->pdo; $applicationCustomRules=[ 'someCustomRule'=>function($value, $prop, $name) use ($pdo) { return /*validate and return error string or null*/; } ]; $validator=new Validator($json, ...$applicationCustomRules); //Which can be used such as: $jQueryOptions=$validator->getJQueryOptions(); $validator->validate($data); $validator->validateProperty('account', $account); } }
  19. NotionCommotion

    Errors turned up by code analyzer.

    What "code analyzer" did you use? If it was part of some framework, then it is implying that you should use the framework methods to access superglobals, set headers, etc. Regarding a few of its other recommendations: require_once. While in the past I often used this, it has been quite a while. Use an autoloader instead and composer whenever applicable. $_POST = array(). With the exception of $_SESSION, I don't think there would ever be a good reason to modify a super global. Global variables whether global by nature or defined so by the developer become a troubleshooting nightmare if they ever have multiple states mysqli_close. Use PDO instead.
  20. NotionCommotion

    Is this a good use for anonymous functions?

    Might or would? May I ask why?
  21. NotionCommotion

    Using Slim and Guzzle as a proxy

    Using Slim to route endpoints to my application. In addition, I have many endpoints (mostly accessed via xhr) which need to be forwarded to another server, and I am using Guzzle to do so. Note only do I have to transfer text/json, I also have to send and retrieve files (currently only csv files, but will later add pdf). Accomplishing this was easier than I expected, but expect I may still be doing certain portions wrong. Anything look off, especially with the multipart forms for file uploads as well as the downloading of files? Thank you <?php $app = new \Slim\App($container); //Local requests $app->get('/settings', function (Request $request, Response $response) { return $this->view->render($response, 'somePage.html',$this->bla->getData()); }); //more local endpoints... $proxyEndpoints=[ '/bla'=>['put'], '/bla/bla/{id:[0-9]+}'=>['delete','put'], '/foo/{id:[0-9]+}'=>['get','put','delete','post'], //more proxy endpoints... ]; foreach ($proxyEndpoints as $route=>$methods) { foreach ($methods as $method) { $app->$method($route, function(Request $request, Response $response) { return $this->remoteServer->proxy($request, $response); //add content type if desired. }); } } <?php class RemoteServer { protected $httpClient, $contentType; public function __construct(\GuzzleHttp\Client $httpClient, string $contentType='application/json') { $this->httpClient=$httpClient; $this->contentType=$contentType; } public function proxy(\Slim\Http\Request $request, \Slim\Http\Response $response, string $contentType=null, \Closure $callback=null):\Slim\Http\Response { $contentType=$contentType??$this->contentType; if($contentType!=='application/json' && $callback) { throw new \Exception('Callback can only be used with contentType application/json'); } $method=$request->getMethod(); $bodyParams=in_array($method,['PUT','POST'])?(array)$request->getParsedBody():[]; //Ignore body for GET and DELETE methods $queryParams=$request->getQueryParams(); $data=array_merge($queryParams, $bodyParams); ///Would be better to write slim's body to guzzle's body so that get parameters are preserved and not overriden by body parameters. $path=$request->getUri()->getPath(); $contentTypeHeader=$request->getContentType(); if(substr($contentTypeHeader, 0, 19)==='multipart/form-data'){ syslog(LOG_INFO, 'contentType: '.$contentTypeHeader); $files = $request->getUploadedFiles(); $multiparts=[]; $errors=[]; foreach($files as $name=>$file) { if ($error=$file->getError()) { $errors[]=[ 'name'=> $name, 'filename'=> $file->getClientFilename(), 'error' => $this->getFileErrorMessage($error) ]; } else { $multiparts[]=[ 'name'=> $name, 'filename'=> $file->getClientFilename(), 'contents' => $file->getStream(), 'headers' => [ //Not needed, right? 'Size' => $file->getSize(), 'Content-Type' => $file->getClientMediaType() ] ]; } } if($errors) return $response->withJson($errors, 422); $multiparts[]=[ 'name'=> 'data', 'contents' => json_encode($data), 'headers' => ['Content-Type' => 'application/json'] ]; $options=['multipart' => $multiparts]; } else { $options = in_array($method,['PUT','POST'])?['json'=>$data]:['query'=>$data]; } try { $curlResponse = $this->httpClient->request($method, $path, $options); } catch (\GuzzleHttp\Exception\RequestException $e) { //Errors only return JSON //Networking error which includes ConnectException and TooManyRedirectsException syslog(LOG_ERR, 'Proxy error: '.$e->getMessage()); if ($e->hasResponse()) { $curlResponse=$e->getResponse(); return $response->withJson(json_decode($curlResponse->getBody()), $curlResponse->getStatusCode()); } else { return $response->withJson($e->getMessage(), $e->getMessage()); } } $statusCode=$curlResponse->getStatusCode(); switch($contentType) { case 'application/json': //Application and server error messages will be returned. Consider hiding server errors. $content=json_decode($curlResponse->getBody()); if($callback) { $content=$callback($content, $statusCode); } return $response->withJson($content, $statusCode); case 'text/html': case 'text/plain': //Application and server error messages will be returned. Consider hiding server errors. $response = $response->withStatus($statusCode); return $response->getBody()->write($curlResponse->getBody()); case 'text/csv': foreach ($response->getHeaders() as $name => $values) { syslog(LOG_INFO, "headers: $name: ". implode(', ', $values)); } if($statusCode===200) { return $response->withHeader('Content-Type', 'application/force-download') ->withHeader('Content-Type', 'application/octet-stream') ->withHeader('Content-Type', 'application/download') ->withHeader('Content-Description', 'File Transfer') ->withHeader('Content-Transfer-Encoding', 'binary') ->withHeader('Content-Disposition', 'attachment; filename="data.csv"') ->withHeader('Expires', '0') ->withHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') ->withHeader('Pragma', 'public') ->withBody($curlResponse->getBody()); } else { return $response->withJson(json_decode($curlResponse->getBody()), $statusCode); } break; default: throw new \Exception("Invalid proxy contentType: $contentType"); } } private function getFileErrorMessage($code){ switch ($code) { case UPLOAD_ERR_INI_SIZE: $message = "The uploaded file exceeds the upload_max_filesize directive in php.ini"; break; case UPLOAD_ERR_FORM_SIZE: $message = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"; break; case UPLOAD_ERR_PARTIAL: $message = "The uploaded file was only partially uploaded"; break; case UPLOAD_ERR_NO_FILE: $message = "No file was uploaded"; break; case UPLOAD_ERR_NO_TMP_DIR: $message = "Missing a temporary folder"; break; case UPLOAD_ERR_CANT_WRITE: $message = "Failed to write file to disk"; break; case UPLOAD_ERR_EXTENSION: $message = "File upload stopped by extension"; break; default: $message = "Unknown upload error"; break; } return $message; } public function callApi(\GuzzleHttp\Psr7\Request $request, array $data=[]):\GuzzleHttp\Psr7\Response { try { $response = $this->httpClient->send($request, $data); } catch (\GuzzleHttp\Exception\ClientException $e) { $response=$e->getResponse(); } catch (\GuzzleHttp\Exception\RequestException $e) { //Networking error which includes ConnectException and TooManyRedirectsException if ($e->hasResponse()) { $response=$e->getResponse(); } else { $response=new \GuzzleHttp\Psr7\Response($e->getCode(), [], $e->getMessage()); } } catch (\GuzzleHttp\Exception\ServerException $e) { //Consider not including all information back to client $response=$e->getResponse(); } return $response; } }
  22. NotionCommotion

    Using Slim and Guzzle as a proxy

    Ran into some odd issues. Things were working, but then all of a sudden the api server couldn't parse the content. Turns out that Guzzle is no longer forwarding the Content-Type header in Slim's request, and I needed to re-apply the Content-Type header. I have no idea what changed to require this as it could be at the browser client, web server/api client, or api server. Any ideas? if((string) $slimRequest->getBody()) $slimRequest=$slimRequest->withHeader('Content-Type', $slimRequest->getContentType());
  23. NotionCommotion

    Using Slim and Guzzle as a proxy

    Turns out this is even easier than I thought. My configuration has two http servers on the same machine where one is mainwebsite.com and it makes curl requests to the other which is api.mainwebsite.com . Now, I am just sending the Slim Request to Guzzle. Originally, I sent Slim's Request with its original host (mainwebsite.com), and my $_SESSION variable kept on getting deleted. I am assuming some sort of web browser or PHP security feature? Any thoughts? Why wasn't the base_uri added to \GuzzleHttp\Client's constructor not being applied? Regardless, I was able to change the host as shown below and all now works. To use less code, I am using a loop to apply the headers to the response. Think I should hard code it like $slimResponse->withHeader('header1', $guzzleResponse->getHeader('header1'))->addMoreHeaders()->thenAddStatusAndBody()? This (I think) will eliminate needing to clone all the responses, however, will will require me to have code for each Content-Type to determine which headers are returned. But maybe that is good and I shouldn't blacklist headers as I am doing but whitelist them? If this approach is acceptable, what other headers should I blacklist? Thanks public function proxy(\Slim\Http\Request $slimRequest, \Slim\Http\Response $slimResponse):\Slim\Http\Response { //Forwards Slim Request to another server and returns the updated Slim Response. $slimRequest=$slimRequest->withUri($slimRequest->getUri()->withHost($this->getHost(false))); //Change slim's host to API server! try { $httpClient = new \GuzzleHttp\Client(['base_uri' => "https://api.mainwebsite.com"]); //Will use injection, and just shown this way ease $guzzleResponse=$httpClient->send($slimRequest); $excludedHeaders=['Date', 'Server', 'X-Powered-By', 'Access-Control-Allow-Origin', 'Access-Control-Allow-Methods', 'Access-Control-Allow-Headers']; $headerArrays=array_diff_key($guzzleResponse->getHeaders(), array_flip($excludedHeaders)); foreach($headerArrays as $headerName=>$headers) { foreach($headers as $headerValue) { $slimResponse=$slimResponse->withHeader($headerName, $headerValue); } } return $slimResponse->withStatus($guzzleResponse->getStatusCode())->withBody($guzzleResponse->getBody()); } catch (\GuzzleHttp\Exception\RequestException $e) { if ($e->hasResponse()) { $guzzleResponse=$e->getResponse(); return $slimResponse->withStatus($guzzleResponse->getStatusCode())->withBody($guzzleResponse->getBody()); } else { return $slimResponse->withStatus(500)->write(json_encode(['message'=>'RequestException without response: '.$e->getMessage()])); } } }
  24. NotionCommotion

    What does "with" imply

    Often, I see "get" and "set" used in a function name, and the intent is obvious. Other times I see "with" (i.e. PSR-7's withScheme, withUserInfo, withHost, withPort, withPath, withQuery, withFragment). It appears to indicate just to clone itself ($this), apply the argument, and return the new object. Am I correct? Thanks
  25. NotionCommotion

    What does "with" imply

    I tend to agree, but didn't want to oppose a better standard. I think the reason they do so is they wish to enforce immutability (which I see value but am still undecided).
×

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.