Advertisement
Guest User

Untitled

a guest
Oct 24th, 2017
112
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 30.67 KB | None | 0 0
  1. <?php
  2. /**
  3. * ======
  4. * FastFM
  5. * ======
  6. * FastFM is a PHP ORM for Filemaker backends heavily inspired
  7. * by Rails' Active Model / Active Record combination.
  8. *
  9. * FastFM REQUIRES the official Filemaker PHP API to work.
  10. * FastFM is just an extension of the official API, NOT
  11. * a replacement.
  12. *
  13. * FastFM is a lightweight yet powerful ORM that allows for easy
  14. * management of records as well as PHP model validation.
  15. *
  16. * @author: James Strong
  17. * @copyright: None!
  18. * @license: Do whatever you want with this. No restrictions whatsoever.
  19. * @version: 1.0 Alpha.
  20. *
  21. * ===============
  22. * Getting Started
  23. * ===============
  24. * FastFM requires little to no setup. All you
  25. * need to do is include the official filemaker api
  26. * as well as this PHP file, set up database credentials and
  27. * your models then you're good to go!
  28. *
  29. * Start by including the libraries required:
  30. *
  31. * // Official Filemaker PHP API
  32. * require_once '/lib/FileMaker.php'
  33. * // FastFM Filemaker ORM
  34. * require_once '/lib/FastFM.php'
  35. *
  36. * Now you have to set up a connection to the filemaker database
  37. * through the FilemakerDB singleton class (part of the framework)
  38. *
  39. * FilemakerDB::setup(host, database, username, password);
  40. *
  41. * Define your model ORM classes and make them extend from FastModel.
  42. * Example:
  43. * class User extends FastModel {
  44. * // Every model must have a default layout
  45. * // to read from.
  46. * public static $layout = 'users';
  47. *
  48. * // FastModel uses a default unique id of 'id'.
  49. * // but can be overrided by defining the static
  50. * // id variable in the child class.
  51. * public static $id = 'user_id';
  52. *
  53. * public function __construct() {
  54. * $this -> validation = array('name' => Validation::$PRESENCE);
  55. * }
  56. * }
  57. *
  58. * And that's about it! You should be able to query filemaker through the User class.
  59. * User::find('13') // searches for row in filemaker with user_id = 13
  60. *
  61. * ============
  62. * CRUD queries
  63. * ============
  64. * FastFM has 5 functions that can be used for creating,
  65. * reading, updating and deleting records.
  66. *
  67. * All standard FileMaker operators for newFindCommand
  68. * can be used.
  69. *
  70. * Any Filemaker Errors that are thrown by the query are thrown as
  71. * Model Exceptions.
  72. *
  73. * -- Find (static) --
  74. * Find is used for searching for models through
  75. * their unique identifier (defined as public static $id on your FastModel).
  76. *
  77. * If no record could be found (Filemaker Error 401),
  78. * the find command will return null.
  79. *
  80. *
  81. * Examples:
  82. * // searches for user with user_id of 1.
  83. * User::find('1')
  84. *
  85. * // searches for user with user_id of 2
  86. * // and uses the CompactUser layout to
  87. * // reduce the number of fields returned.
  88. * User::find('2', 'CompactUser')
  89. *
  90. * // Throws ModelException.
  91. * User::find('error')
  92. *
  93. *
  94. * -- Where (static) --
  95. * Similar to find, but instead searches based on the array of criteria
  96. * passed into the function.
  97. *
  98. * Examples:
  99. * // Returns an array of Users who have "Strong" in their name.
  100. * User::where(array('name' => 'Strong'));
  101. *
  102. * // Returns a User object from the first record that has the email
  103. * // james.strong@etherpros.com
  104. * User::where(array('email' => 'james.strong@etherpros.com'), true);
  105. *
  106. *
  107. *
  108. * -- Create (static) --
  109. * Receives a list of field/value parameters and creates a new FastModel
  110. * object as well as a database record based on those parameters.
  111. *
  112. * Example:
  113. * User::create(array('name' => 'James Strong', 'email' => 'james.strong@etherpros.com'));
  114. *
  115. *
  116. * -- All (static) --
  117. * Performs a standard FindAllCommand and returns an array of your ORM object.
  118. *
  119. * Example:
  120. * User::all();
  121. *
  122. *
  123. * -- Update --
  124. * Updates FilemakerDB with changes done to model.
  125. * Returns true or false.
  126. *
  127. * Example:
  128. * $user = User::find('1');
  129. * $user -> email = 'updated@email.com';
  130. * $user -> update();
  131. *
  132. *
  133. * -- Update Attributes --
  134. * Same as Update, but receives a list of attributes
  135. * to update instead of updating dirty fields.
  136. *
  137. * Example:
  138. * $user = User::find('1');
  139. * $user -> update_attributes(array('email' => 'updated@email.com'));
  140. *
  141. *
  142. *
  143. * -- Save --
  144. * Performs update() if model points to an existing database entry.
  145. * Performs create() if model is a new model.
  146. *
  147. * Examples:
  148. * (create example)
  149. * $user = new User();
  150. * $user -> name = "James Strong";
  151. * $user -> email = 'james.strong@etherpros.com';
  152. * $user -> save();
  153. *
  154. *
  155. * ==========
  156. * Validation
  157. * ==========
  158. * FastFM has some basic support for validation.
  159. * Not very extensive at the moment, but still useful.
  160. *
  161. * FastFM supports these 3 validation methods out of the box.
  162. * Validation::$PRESENCE
  163. * Validation::$UNIQUENESS
  164. * // email regexp check.
  165. * Validation::$EMAIL_FORMAT
  166. *
  167. * In order to use these methods you have to define
  168. * the validation array in the constructor of your FastFM class.
  169. *
  170. * Example:
  171. * class User {
  172. * public function __construct() {
  173. * $this -> validation = array('name' => Validation::$PRESENCE,
  174. * 'email' => array(Validation::$PRESENCE, Validation::$EMAIL_FORMAT),
  175. * 'username' => Validation::$UNIQUENESS);
  176. * }
  177. * }
  178. *
  179. * The array key is the field you want to validate on and the value is the validation function.
  180. * In order to have more than one validation for a given field
  181. * you must pass in a sub-array as a value (see email example).
  182. *
  183. *
  184. * If you need more validation, you can define your own custom functions to be validated on.
  185. * Validation::$CUSTOM_FUNCTION = 'function on your model'
  186. *
  187. * The function defined on your model receives one parameter, the validation object on which
  188. * you add errors per your validation.
  189. *
  190. * Example:
  191. * Validation::$CUSTOM_FUNCTION = 'username_no_spaces'
  192. *
  193. * class User {
  194. * // Checks if there are blank spaces inside of username
  195. * public function username_no_spaces($validation) {
  196. * $pos = strpos($value, ' ');
  197. * // if some spaces were found
  198. * if ($pos){
  199. * $this -> addError("Username", "Username can't have spaces");
  200. * return false;
  201. * }
  202. * }
  203. * }
  204. *
  205. * ==========
  206. * That's it!
  207. * ==========
  208. * That's it for now!
  209. * But don't worry, FastFM is under active development
  210. * and there will be more updates soon!
  211. *
  212. * You can follow me on github (username laspluviosillas)
  213. * if you are interested in contributing.
  214. */
  215.  
  216. public function checkForSpaces($value) {
  217. $pos = strpos($value, ' ');
  218. if ($pos){
  219. $this -> addError("Username","Username can't have spaces");
  220. return false;
  221. }
  222. return true;
  223. }
  224.  
  225. //== code starts here ==
  226. /**
  227. * ==========
  228. * Fast Model
  229. * ==========
  230. * Base class that all ORM Models extend from.
  231. */
  232. class FastModel {
  233.  
  234. /**
  235. * Default Filemaker layout that will be queried
  236. * for this class. Example: TrainSession.class.php
  237. * has a default layout of TrainSession.
  238. */
  239. public static $layout;
  240.  
  241. /**
  242. * Primary field of the object in Filemaker.
  243. * Used for the find function.
  244. * Example: TrainSession -> uniqueID = 'SessionID'.
  245. */
  246. public static $id = 'id';
  247.  
  248. /** FileMaker DB fields are stored in this hash. **/
  249. public $fields = array();
  250.  
  251. /**
  252. * Hash that contains fields mapped to their validation functions
  253. * to be used by the Validation.
  254. *
  255. * Example:
  256. * array('firstName' => Validation::$PRESENCE,
  257. * 'lastName' => Validation::$PRESENCE,
  258. * 'email' => array(Validation::$EMAIL_FORMAT, Validation::$UNIQUENESS),
  259. * );
  260. */
  261. public $validation;
  262.  
  263. /** Contains validation errors after a call to validate(). **/
  264. public $errors;
  265.  
  266. /** Determines whether model already exists in the database or not **/
  267. public $persisted = false;
  268.  
  269.  
  270. /**
  271. * Original FilemakerRecord id from fin queries.
  272. * Keeping this object in memory prevents an extra find query
  273. * from being needed on the update() and save() functions.
  274. **/
  275. public $record_id;
  276.  
  277. /** Stores variables that have been updated,
  278. * Used when saving or updating object in order
  279. * to only send the variables that need to be updated
  280. * instead of the entire object.
  281. **/
  282. public $dirty = array();
  283.  
  284. /** Instantiates variables for any attributes sent in **/
  285. function __construct($attributes = array()) {
  286. foreach($attributes as $key => $value) {
  287. $this -> {$key} = $value;
  288. }
  289. }
  290.  
  291.  
  292. /** Dynamic getters and setters for filemaker fields **/
  293. public function __get($name) {
  294. return $this -> fields[$name];
  295. }
  296.  
  297. public function __set($name, $value) {
  298. // set field to dirty if model is persisted and field has changed.
  299. if($this -> fields[$name] != $value) {
  300. $this -> dirty[$name] = true;
  301. }
  302.  
  303. $this -> fields[$name] = $value;
  304. }
  305.  
  306. /** Resets dirty tracking array.
  307. Called after an update or save **/
  308. private function clean_dirt() {
  309. $this -> dirty = array();
  310. }
  311.  
  312. /** Returns true if dirty array is not of size 0 **/
  313. public function is_dirty() {
  314. return sizeof($this -> dirty) != 0;
  315. }
  316.  
  317. /**
  318. * Returns validation object based on the 'validation' hash.
  319. */
  320. public function validate() {
  321. $validation = new Validation();
  322. $validation -> setModel($this);
  323. $validation -> validate();
  324. $this -> errors = $validation -> getErrors();
  325. return $validation;
  326. }
  327.  
  328. /** Returns boolean value if the model doesn't have
  329. any errors from its last validation check through
  330. validate().
  331. */
  332. public function is_valid() {
  333. return sizeof($this -> errors) == 0;
  334. }
  335.  
  336. /** Saves changed fields on object to the database. **/
  337. public function update() {
  338. // throw an exception if the object is a new object
  339. // and doesn't correspond to an already-existing database
  340. // record.
  341. if(!$this -> persisted) {
  342. throw new ModelException('Update cannot be called on non-persisted objects');
  343. return true;
  344. }
  345.  
  346. // if the object is not dirty, there is nothing to update!
  347. if(!$this -> is_dirty()) {
  348. return true;
  349. }
  350.  
  351. // validate values before update!
  352. $this -> validate();
  353.  
  354. // return false if invalid because update failed.
  355. if(!$this -> is_valid()) {
  356. return false;
  357. }
  358.  
  359. $query = new FilemakerQuery(static::$layout);
  360.  
  361. // Get record for this object.
  362. // Use record_id if present since it is a faster search
  363. // otherwise use unique id defined on the table.
  364. // Which is not the same as the Filemaker unique id, strangely enough.
  365. if($this -> record_id != null) {
  366. $record = $query -> findById($this -> record_id);
  367. } else {
  368. // If no record_id, do standard find command on model's unique id.
  369. $id = $this -> get_id();
  370. $record = $query -> findFirst(array(static::$id =>$id)) -> getFirstRecord();
  371. }
  372.  
  373. // serialize object to array to send into
  374. // filemaker query.
  375. $criteria = $this -> to_dirty_array();
  376.  
  377. // perform update
  378. $query = new FilemakerQuery(static::$layout);
  379. $query -> update($criteria, $record);
  380.  
  381. // clean dirty array since model was just updated.
  382. $this -> clean_dirt();
  383.  
  384. // everything was successful! (in theory).
  385. return true;
  386. }
  387.  
  388. /** Extension of update() above. Receives a hash of attributes
  389. * populates the model with those attributes and then calls
  390. * update().
  391. */
  392. public function update_attributes($attributes) {
  393. $this -> set_attributes($attributes);
  394. return $this -> update();
  395. }
  396.  
  397. /** Save is really just an alias for create and update.
  398. * Create if model is not persisted.
  399. * Update if it is.
  400. *
  401. * Example:
  402. *
  403. * Create example:
  404. * $login = new Login();
  405. * $login -> name = 'James Strong'
  406. * $login -> email = 'james.strong@etherpros.com'
  407. * $login -> save();
  408. * // returns true or false.
  409. *
  410. * Update example:
  411. * $login = Login::find('1');
  412. * $login -> name = 'I have a new name!';
  413. * $login -> save();
  414. * // also returns true or false.
  415. **/
  416. public function save() {
  417. // if persisted just route to update();
  418. if($this -> persisted) {
  419. return $this -> update();
  420. } else {
  421. // perform create.
  422. // In the case of create, since dirty-tracking
  423. // is not applicable, the entire fields array
  424. // is sent as criteria to be saved.
  425. $criteria = $this -> fields;
  426. $this -> validate();
  427. if($this -> is_valid()) {
  428. $query = new FilemakerQuery(static::$layout);
  429. $query -> create($criteria);
  430. return true;
  431. }
  432. //if invalid return false
  433. return false;
  434. }
  435. }
  436.  
  437. /** Static create. Calls sets attributes on a new ORM model instance and then calls save() on it.
  438. * Example:
  439. * $login = Login::create('name' => 'James Strong', 'email' => 'james.strong@etherpros.com');
  440. * if($login -> is_valid()) {
  441. * echo 'login created';
  442. * } else {
  443. * print_r($login -> errors);
  444. * }
  445. **/
  446. public static function create($criteria) {
  447. $instance = self::instantiate();
  448. $instance -> set_attributes($criteria);
  449. $instance -> save();
  450. return $instance;
  451. }
  452.  
  453. private function set_attributes($attributes) {
  454. foreach($attributes as $key => $value) {
  455. // It is important to set the attribute using
  456. // $this -> {$key} syntax and NOT by setting
  457. // the fields array directly like $this -> fields[$key].
  458. // The latter method calls the __set function which includes
  459. // dirty tracking, while the former skips all the dirty tracking
  460. // checks.
  461. $this -> {$key} = $value;
  462. }
  463. }
  464.  
  465. /** Returns array of all objects **/
  466. public static function all($layout = null) {
  467. if($layout == null) {
  468. $layout = static::$layout;
  469. }
  470. $query = new FilemakerQuery($layout);
  471. $records = $query -> all() -> getRecords();
  472. return self::unserialize_many($records);
  473. }
  474.  
  475. /**
  476. * Performs a filemaker find command based on the ID passed in.
  477. * Searches on the primary field (defined by uniqueID) of the layout.
  478. * Uses default layout for the model unless one is specified.
  479. * Example:
  480. * $login = Login::find('301');
  481. */
  482. public static function find($id, $layout = null) {
  483. if($layout == null) {
  484. $layout = static::$layout;
  485. }
  486. $query = new FilemakerQuery($layout);
  487. $record = $query -> findFirst(array(static::$id => $id)) -> getFirstRecord();
  488. return self::unserializer($record);
  489. }
  490.  
  491. /**
  492. * Performs a filemaker find command based on the hash of criteria passed in.
  493. * $firstOnly returns only the first record if set to true.
  494. * $layout overrides default layout to class.
  495. *
  496. * Example:
  497. * $logins = Login::where(array('FirstName' => 'James', 'Status' => 'Active'));
  498. */
  499. public static function where($criteria, $firstOnly=false, $layout=null) {
  500. if($layout == null) {
  501. $layout = static::$layout;
  502. }
  503. $query = new FilemakerQuery($layout);
  504. if($firstOnly) {
  505. $record = $query -> findFirst($criteria) -> getFirstRecord();
  506. // return unserialized object
  507. return self::unserializer($record);
  508. }else{
  509. $records = $query -> find($criteria) -> getRecords();
  510. // Just return an empty array if no records were found
  511. if($records == null) { return array(); };
  512. return self::unserialize_many($records);
  513. }
  514. }
  515.  
  516. /** Unserializes an array of Filemaker_Result objects **/
  517. private static function unserialize_many($records) {
  518. $results_array = array();
  519. foreach($records as $record) {
  520. array_push($results_array, self::unserializer($record));
  521. }
  522. return $results_array;
  523. }
  524.  
  525.  
  526. /**
  527. * Static version of unserialize method below.
  528. *
  529. * NOTE:
  530. * For some completely unknown reason, PHP
  531. * doesn't allow for there two be 2 methods with the
  532. * same name even though one is a static method
  533. * and the other is an instance method.
  534. *
  535. * Example:
  536. * private static function unserialize()
  537. * private function unserialize()
  538. * - throws error message -
  539. *
  540. * Due to this naming restriction, I had to
  541. * name the class (static) level unserialize method
  542. * 'unserializer' instead,
  543. *
  544. **/
  545. private static function unserializer($record) {
  546. $instance = self::instantiate();
  547. $instance -> unserialize($record);
  548. return $instance;
  549. }
  550.  
  551. /** Unserializes Filemaker_Result object and populates fields hash. **/
  552. private function unserialize($record) {
  553. $fields = $record -> getFields();
  554. foreach($fields as $field) {
  555. $this -> fields[$field] = $record -> getField($field);
  556. }
  557.  
  558. // filemaker unique record id.
  559. $this -> record_id = $record -> getRecordId();
  560.  
  561. // set instance to persisted since it was unserialized
  562. // from the database.
  563. $this -> persisted = true;
  564. }
  565.  
  566. /** Serializes all ORM fields to a a field name / value hash of changed fields.
  567. * Function is used to do update commands from an
  568. * ORM object to Filemaker.
  569. * Only returns dirty fields.
  570. */
  571. public function to_dirty_array() {
  572. $serialized = array();
  573. foreach($this -> dirty as $key => $value) {
  574. $serialized[$key] = $this -> fields[$key];
  575. }
  576. return $serialized;
  577. }
  578.  
  579. /** Creates new instance of this object from the static scope **/
  580. public static function instantiate() {
  581. $class = static::klass();
  582. $instance = new $class();
  583. return $instance;
  584. }
  585.  
  586. /** Simple alias for retrieving unique id **/
  587. public function get_id() {
  588. return $this -> {static::$id};
  589. }
  590.  
  591. /** Simple alias for retrieving the class name **/
  592. public static function klass() {
  593. return get_called_class();
  594. }
  595.  
  596. }
  597.  
  598. /**
  599. *
  600. * ===============
  601. * Filemaker Query
  602. * ==============
  603. * Filemaker Query is just a helper class that
  604. * adds a layer of abstraction for performing filemaker commands.
  605. * This class is heavily used by FastModel for its basic ORM queries.
  606. *
  607. */
  608. class FilemakerQuery {
  609. private static $VALID = "VALID";
  610. private static $BLANK = "BLANK";
  611. private static $ERROR = "ERROR";
  612.  
  613. public $connection = null;
  614. public $layout = null;
  615. public $sortRules = null;
  616.  
  617. public function __construct($layout) {
  618. $this -> connection = FilemakerDB::getConnection();
  619. $this -> layout = $layout;
  620. }
  621. public function setSortRules($sortRules) {
  622. $this -> sortRules = $sortRules;
  623. }
  624.  
  625. /** Basic update command. Receives a hash of parameters
  626. * and returns a Filemaker_Result object.
  627. * Example:
  628. * $query = new FilemakerQuery('Logins');
  629. * $criteria = array('FullName' => 'James Strong');
  630. * $record = $query -> findFirst($criteria) -> getFirstRecord();
  631. * $query -> update(array('FullName' => 'John Strong'));
  632. */
  633. public function update($criteria, $record) {
  634. foreach($criteria as $key=>$value) {
  635. //echo $key . " " . $value . "\n";
  636. $record -> setField($key, $value);
  637. }
  638. $result = $record -> commit();
  639. return $result;
  640. }
  641.  
  642. /** Basic create command. Receives a hash of parameters
  643. * and returns a Filemaker_Result object.
  644. * Example:
  645. * $query = new FilemakerQuery('NewLMSCopy');
  646. * $query -> create(array("Login_ID" => 'id value'));
  647. */
  648. public function create($criteria) {
  649. $record =& $this -> connection -> newAddCommand($this -> layout, array());
  650. foreach($criteria as $key => $value) {
  651. $record -> setField($key, $value);
  652. }
  653. $result = $record -> execute();
  654. return $result;
  655. }
  656.  
  657. /** Same as find command, just with a max cap of one **/
  658. public function findFirst($criteria) {
  659. return $this -> find($criteria, 1);
  660. }
  661.  
  662. /** Performs a Find All FileMaker command **/
  663. public function all() {
  664. $findCommand =& $this -> connection -> newFindAllCommand($this -> layout);
  665. $result = $findCommand -> execute();
  666. $this -> validateResult($result);
  667. return $result;
  668. }
  669.  
  670. /** Equivalent to FileMaker's getRecordById function **/
  671. public function findById($id) {
  672. $result = $this -> connection -> getRecordById($this -> layout, $id);
  673. $this -> validateResult($result);
  674. return $result;
  675. }
  676.  
  677. /** Basic find command. Receives a hash of parameters
  678. * and returns a Filemaker_Result object.
  679. *
  680. * $criteria: Hash of values used for the find command.
  681. * $max: Max number of records to be retrieved. -1 means no cap.
  682. *
  683. * Compound finds are performed by passing in arrays of arrays.
  684. *
  685. * Examples:
  686. * $query = new FilemakerQuery('Logins');
  687. * $result = $query -> find(array('LastName' => 'Strong')
  688. *
  689. * Compound find example:
  690. * $query = new FilemakerQuery('TrainSession');
  691. * $criteria = array(
  692. * array("Login_ID" => "=".$loginID, "Status" => "Assigned"),
  693. * array("Login_ID" => "=".$loginID, "Status" => "Live")
  694. * );
  695. * $result = $query -> find($criteria, -1);
  696. */
  697. public function find($criteria, $max = -1, $loud=false) {
  698. // if the array is multi-dimensional, this is a compound find.
  699. $compound = is_array($criteria[0]);
  700.  
  701. if(!$compound) {
  702. $findCommand =& $this -> connection -> newFindCommand($this -> layout);
  703. //if not a compound find command, then add the criteria directly to the find command.
  704. foreach($criteria as $key => $value) {
  705. $findCommand -> addFindCriterion($key, $value);
  706. }
  707. } else {
  708. $findCommand =& $this -> connection -> newCompoundFindCommand($this -> layout);
  709. //if a compound find command, then create find requests, add criteria to the requests.
  710. //then add the requests to the find command.
  711. $i = 0;
  712. foreach($criteria as $set) {
  713. $i++;
  714. $findRequest =& $this -> connection -> newFindRequest($this -> layout);
  715. foreach($set as $key => $value) {
  716. $findRequest -> addFindCriterion($key, $value);
  717. }
  718. $findCommand -> add($i, $findRequest);
  719. }
  720. }
  721. // Set max number of records if present.
  722. if($max != -1) {
  723. $findCommand -> setRange(0, $max);
  724. }
  725. // Set sort rules.
  726. if($this -> sortRules) {
  727. $i = 0;
  728. foreach($this -> sortRules as $key => $value) {
  729. $i++;
  730. $findCommand -> addSortRule($key, 1, $value);
  731. }
  732. }
  733. $result = $findCommand -> execute();
  734. //HACK: return empty result object if no records found instead of a filemaker error.
  735. if($this -> validateResult($result) == self::$BLANK && $loud == false) {
  736. return new Filemaker_Result($this -> connection);
  737. }
  738. return $result;
  739. }
  740.  
  741. /** Checks Filemaker_Result to make sure it is not an error.
  742. * When loud is set to true, this function will throw
  743. * an exception if the result is an error.
  744. *
  745. * When loud is set to false, the function will
  746. * only return true or false depending on whether
  747. * the result is an error or not.
  748. *
  749. * Error 401 has been overriden to return BLANK instead of ERROR.
  750. * Exceptions are only thrown for critical errors that shouldn't happen.
  751. * On the other hand, 401 errors can happen all the time if a search
  752. * query doesn't return any results. This is normal behavior and should not throw an exception.
  753. */
  754. public function validateResult($result, $loud=true) {
  755. $isError = FileMaker::isError($result);
  756. if($isError == true) {
  757. // 401 is a no record found error, so we don't want to throw a model exception for this.
  758. if ( $result->getCode() == "401" ) {
  759. return self::$BLANK;
  760. }else{
  761. //only throw an exception if this is a loud validation
  762. if($loud) {
  763. throw new ModelException("Error retrieving information from database: " . $result -> getCode(),
  764. $result -> getCode());
  765. }
  766. return self::$ERROR;
  767. }
  768. }
  769. if($isError == false) {
  770. return self::$VALID;
  771. }
  772. }
  773. }
  774.  
  775. /**
  776. * ============
  777. * Filemaker DB
  778. * ============
  779. * Singleton that lazy-loads connection to FileMaker DB
  780. **/
  781. class FilemakerDB {
  782. // database connection details
  783. public static $host = 'localhost';
  784. public static $database = null;
  785. public static $username = '';
  786. public static $password = '';
  787.  
  788. private static $connection = null;
  789.  
  790. // Private constructor for singleton pattern
  791. private function __construct() {
  792. }
  793.  
  794. /** Values required for setting up the connection. **/
  795. public static function setup($host, $database, $username, $password) {
  796. self::$host = $host;
  797. self::$database = $database;
  798. self::$username = $username;
  799. self::$password = $password;
  800. }
  801.  
  802. // lazy-loads filemaker database connection
  803. public static function getConnection() {
  804. if (!self::$connection)
  805. {
  806. self::$connection = new FileMaker();
  807. self::$connection->setProperty('database', self::$database);
  808. self::$connection->setProperty('hostspec', self::$host);
  809. self::$connection->setProperty('username', self::$username);
  810. self::$connection->setProperty('password', self::$password);
  811.  
  812. }
  813. return self::$connection;
  814. }
  815.  
  816. // clone private for singleton pattern
  817. private function __clone(){
  818. }
  819. }
  820.  
  821. /* Stub exception class for Filemaker Model exceptions */
  822. class ModelException extends Exception {}
  823.  
  824.  
  825.  
  826. /**
  827. *
  828. * ===========
  829. * Validation
  830. * ===========
  831. * This class is used for model-level validation.
  832. * It also is able to generate a generic validation view.
  833. *
  834. * This class is heavily used by ModelBase for validating models
  835. * before persisting them to the database.
  836. *
  837. */
  838. class Validation {
  839. public static $PRESENCE = "presenceValidation";
  840. public static $UNIQUENESS = "uniquenessValidation";
  841. public static $EMAIL_FORMAT = "emailFormatValidation";
  842.  
  843. /*
  844.  
  845. $CUSTOM_FUNCTION is an exception.
  846. All of the constants defined above call functions that are defined in this class.
  847.  
  848. $CUSTOM_FUNCTION instead calls a function defined in the MODEL being validated.
  849.  
  850. Pseudo-code example:
  851. class MyCustomClass extends FastModel {
  852. public static $validation = array(Validation::$CUSTOM_FUNCTION, 'iWillAlwaysFail');
  853. public function iWillAlwaysFail($validation) {
  854. $validation -> addError('Always Fail', 'I am a custom function that always fails');
  855. }
  856. }
  857.  
  858. When validate() is called on an instance of MyCustomClass,
  859. iWillAlwaysFail which is defined in our MyCustomClass will be called,
  860. and the validation will return "I am a custom function that always fails".
  861.  
  862. */
  863. public static $CUSTOM_FUNCTION = "CustomValidationFunctionForModel";
  864.  
  865. private $model = null;
  866. private $action = null;
  867. private $errors = array();
  868.  
  869. /** Goes through the validation array inside of $model
  870. * and calls the requires validation functions.
  871. *
  872. * Refer to FastModel for details on how the validation hash is defined.
  873. *
  874. * Example:
  875. * $attendee = new Attendee();
  876. *
  877. * // Is going to fail, because Attendee requires
  878. * // first name and last name presence.
  879. * $attendee -> setFirstName('');
  880. * $attendee -> setLastName('');
  881. * $attendee -> setEmail('totally@unique.com');
  882. *
  883. * $validation = new Validation();
  884. * $validation -> setModel($login);
  885. * $validation -> validate();
  886. *
  887. * // Returns First Name and Last Name can't be blank.
  888. */
  889. public function validate() {
  890. foreach($this -> model -> validation as $key => $value) {
  891. if( is_array($value) ) {
  892. foreach ($value as $entry) {
  893. $this -> validation_call($key, $entry);
  894. }
  895. }else{
  896. $this -> validation_call($key, $value);
  897. }
  898. }
  899. }
  900.  
  901. private function validation_call($key, $value) {
  902. // if the model is persisted to the database
  903. // then we only want to call validation on
  904. // dirty fields, since we know that non-dirty fields are valid.
  905. // So, if the model is persisted but the field being validated is NOT
  906. // dirty, skip validation call.
  907. if($this -> model -> persisted && $this -> model -> dirty[$key] != true) {
  908. return;
  909. }
  910.  
  911. if($key != self::$CUSTOM_FUNCTION) {
  912. $this -> $value($key);
  913. } else {
  914. $this -> model -> $value($this);
  915. }
  916. }
  917.  
  918. private function uniquenessValidation($key) {
  919. $value = $this -> model -> { $key };
  920. // uniqueness validation does NOT manage blank values.
  921. // doing a record search with a blank value returns all records.
  922. // so the uniqueness will always fail.
  923. if(!empty($value)) {
  924. $class = $this -> model -> klass();
  925. $this -> checkUniquenessFor($key, $value, $class::$layout);
  926. }
  927. }
  928. private function presenceValidation($key) {
  929. $value = $this -> model -> {$key};
  930. $this -> checkPresence($key, $value);
  931. }
  932. private function emailFormatValidation($key) {
  933. $value = $this -> model -> {$key};
  934. $this -> checkEmail($value);
  935. }
  936.  
  937. public function validatePasswordEquality($pwd, $pwd_confirm) {
  938. // if password values do not match
  939. if($pwd != $pwd_confirm) {
  940. //send error of passwords not matching.
  941. $this -> addError('Password', "Password and Password confirmation fields do not match.");
  942. return false;
  943. }
  944. return true;
  945. }
  946. public function checkPresence($key, $value) {
  947. if(empty( $value )) {
  948. $this -> addError($key, $key . " can't be blank");
  949. return false;
  950. }
  951. return true;
  952. }
  953.  
  954. public function checkEmail($email) {
  955. if(!empty($email)) {
  956. $result = TRUE;
  957. if(!eregi("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$", $email)) {
  958. $result = FALSE;
  959. }
  960. if(!$result) {
  961. $this -> addError('Email', "Email is not a valid format.");
  962. }
  963. return $result;
  964. }
  965. }
  966.  
  967. public function checkUniquenessFor($key, $value, $table) {
  968. //escape @ characters
  969. $value = str_replace("@", "\@", $value);
  970. $query = new FilemakerQuery($table);
  971. $records = $query -> findFirst(array($key => "=".$value)) -> getRecords();
  972. $record_count = 0;
  973. foreach ((array) $records as $record) {
  974. $record_count++;
  975. }
  976. //return true of false depending on whether field is already present in the DB.
  977. $unique = ($record_count > 0) ? false : true;
  978. if(!$unique) $this -> addError($key, "This " . $key . " is already taken.");
  979. return $unique;
  980. }
  981.  
  982. // ===================
  983. // Getters and Setters
  984. // ===================
  985. public function setModel($model) { $this -> model = $model; }
  986. public function getModel() { return $model; }
  987.  
  988. public function getErrors() {
  989. return $this->errors;
  990. }
  991. public function addError($key,$value) {
  992. array_push($this->errors, new ValidationError($key, $value));
  993. }
  994. public function isValid() {
  995. return sizeof($this->errors) == 0;
  996. }
  997. public function numberOfErrors() {
  998. return sizeof($this->errors);
  999. }
  1000. public function setErrors($errors) {
  1001. $this->errors = $errors;
  1002. }
  1003.  
  1004. }
  1005.  
  1006. class ValidationError {
  1007. public $key = "";
  1008. public $value = "";
  1009. public function __construct($key,$value) {
  1010. $this -> key = $key;
  1011. $this -> value = $value;
  1012. }
  1013. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement