| | 234 | |
| | 235 | /** |
| | 236 | * TCacheDependency class. |
| | 237 | * |
| | 238 | * TCacheDependency is the base class implementing {@link ICacheDependency} interface. |
| | 239 | * Descendant classes must implement {@link checkChanges()} to provide |
| | 240 | * actual dependency checking logic. |
| | 241 | * |
| | 242 | * The property value of {@link getHasChanged HasChanged} tells whether |
| | 243 | * the dependency is changed or not. |
| | 244 | * |
| | 245 | * You may disable the dependency checking by setting {@link setEnabled Enabled} |
| | 246 | * to false. |
| | 247 | * |
| | 248 | * Note, since the dependency objects often need to be serialized so that |
| | 249 | * they can persist across requests, you may need to implement __sleep() and |
| | 250 | * __wakeup() if the dependency objects contain resource handles which are |
| | 251 | * not serializable. |
| | 252 | * |
| | 253 | * Currently, the following dependency classes are provided in the PRADO release: |
| | 254 | * - {@link TFileCacheDependency}: checks whether a file is changed or not |
| | 255 | * - {@link TDirectoryCacheDependency}: checks whether a directory is changed or not |
| | 256 | * - {@link TGlobalStateCacheDependency}: checks whether a global state is changed or not |
| | 257 | * - {@link TChainedCacheDependency}: checks whether any of a list of dependencies is changed or not |
| | 258 | * |
| | 259 | * @author Qiang Xue <qiang.xue@gmail.com> |
| | 260 | * @version $Id $ |
| | 261 | * @package System.Caching |
| | 262 | * @since 3.1.0 |
| | 263 | */ |
| | 264 | abstract class TCacheDependency extends TComponent implements ICacheDependency |
| | 265 | { |
| | 266 | private $_enabled=true; |
| | 267 | |
| | 268 | /** |
| | 269 | * @return boolean whether this dependency is enabled. Default value is true. |
| | 270 | */ |
| | 271 | public function getEnabled() |
| | 272 | { |
| | 273 | return $this->_enabled; |
| | 274 | } |
| | 275 | |
| | 276 | /** |
| | 277 | * Sets a value indicating whether this cache dependency is enabled or not. |
| | 278 | * Cache dependency checking is only performed when it is enabled. |
| | 279 | * @param boolean whether this dependency is to be enabled. |
| | 280 | */ |
| | 281 | public function setEnabled($value) |
| | 282 | { |
| | 283 | $this->_enabled=TPropertyValue::ensureBoolean($value); |
| | 284 | } |
| | 285 | |
| | 286 | /** |
| | 287 | * @return boolean whether the dependency is changed or not. |
| | 288 | * If the dependency checking is disabled, it always returns false. |
| | 289 | */ |
| | 290 | public function getHasChanged() |
| | 291 | { |
| | 292 | return $this->_enabled ? $this->checkChanges() : false; |
| | 293 | } |
| | 294 | |
| | 295 | /** |
| | 296 | * Performs the actual dependency checking. |
| | 297 | * This method must be implemented by child classes. |
| | 298 | * @return boolean whether the dependency is changed or not. |
| | 299 | */ |
| | 300 | abstract protected function checkChanges(); |
| | 301 | } |
| | 302 | |
| | 303 | |
| | 304 | /** |
| | 305 | * TFileCacheDependency class. |
| | 306 | * |
| | 307 | * TFileCacheDependency performs dependency checking based on the |
| | 308 | * last modification time of the file specified via {@link setFileName FileName}. |
| | 309 | * The dependency is reported as unchanged if and only if the file's |
| | 310 | * last modification time remains unchanged. |
| | 311 | * |
| | 312 | * @author Qiang Xue <qiang.xue@gmail.com> |
| | 313 | * @version $Id $ |
| | 314 | * @package System.Caching |
| | 315 | * @since 3.1.0 |
| | 316 | */ |
| | 317 | class TFileCacheDependency extends TCacheDependency |
| | 318 | { |
| | 319 | private $_fileName; |
| | 320 | private $_timestamp; |
| | 321 | |
| | 322 | /** |
| | 323 | * Constructor. |
| | 324 | * @param string name of the file whose change is to be checked. |
| | 325 | */ |
| | 326 | public function __construct($fileName) |
| | 327 | { |
| | 328 | $this->setFileName($fileName); |
| | 329 | } |
| | 330 | |
| | 331 | /** |
| | 332 | * @return string the name of the file whose change is to be checked |
| | 333 | */ |
| | 334 | public function getFileName() |
| | 335 | { |
| | 336 | return $this->_fileName; |
| | 337 | } |
| | 338 | |
| | 339 | /** |
| | 340 | * @param string the name of the file whose change is to be checked |
| | 341 | */ |
| | 342 | public function setFileName($value) |
| | 343 | { |
| | 344 | $this->_fileName=$value; |
| | 345 | $this->_timestamp=@filemtime($value); |
| | 346 | } |
| | 347 | |
| | 348 | /** |
| | 349 | * @return int the last modification time of the file |
| | 350 | */ |
| | 351 | public function getTimestamp() |
| | 352 | { |
| | 353 | return $this->_timestamp; |
| | 354 | } |
| | 355 | |
| | 356 | /** |
| | 357 | * Performs the actual dependency checking. |
| | 358 | * This method returns true if the last modification time of the file is changed. |
| | 359 | * @return boolean whether the dependency is changed or not. |
| | 360 | */ |
| | 361 | protected function checkChanges() |
| | 362 | { |
| | 363 | return @filemtime($this->_fileName)!==$this->_timestamp; |
| | 364 | } |
| | 365 | } |
| | 366 | |
| | 367 | /** |
| | 368 | * TDirectoryCacheDependency class. |
| | 369 | * |
| | 370 | * TDirectoryCacheDependency performs dependency checking based on the |
| | 371 | * modification time of the files contained in the specified directory. |
| | 372 | * The directory being checked is specified via {@link setDirectory Directory}. |
| | 373 | * |
| | 374 | * By default, all files under the specified directory and subdirectories |
| | 375 | * will be checked. If the last modification time of any of them is changed |
| | 376 | * or if different number of files are contained in a directory, the dependency |
| | 377 | * is reported as changed. By specifying {@link setRecursiveCheck RecursiveCheck} |
| | 378 | * and {@link setRecursiveLevel RecursiveLevel}, one can limit the checking |
| | 379 | * to a certain depth of the subdirectories. |
| | 380 | * |
| | 381 | * @author Qiang Xue <qiang.xue@gmail.com> |
| | 382 | * @version $Id $ |
| | 383 | * @package System.Caching |
| | 384 | * @since 3.1.0 |
| | 385 | */ |
| | 386 | class TDirectoryCacheDependency extends TCacheDependency |
| | 387 | { |
| | 388 | private $_recursiveCheck=true; |
| | 389 | private $_recursiveLevel=-1; |
| | 390 | private $_timestamps; |
| | 391 | private $_directory; |
| | 392 | |
| | 393 | /** |
| | 394 | * Constructor. |
| | 395 | * @param string the directory to be checked |
| | 396 | */ |
| | 397 | public function __construct($directory) |
| | 398 | { |
| | 399 | $this->setDirectory($directory); |
| | 400 | } |
| | 401 | |
| | 402 | /** |
| | 403 | * @return string the directory to be checked |
| | 404 | */ |
| | 405 | public function getDirectory() |
| | 406 | { |
| | 407 | return $this->_directory; |
| | 408 | } |
| | 409 | |
| | 410 | /** |
| | 411 | * @param string the directory to be checked |
| | 412 | * @throws TInvalidDataValueException if the directory does not exist |
| | 413 | */ |
| | 414 | public function setDirectory($directory) |
| | 415 | { |
| | 416 | if(($path=realpath($directory))===false || !is_dir($path)) |
| | 417 | throw new TInvalidDataValueException('directorycachedependency_directory_invalid',$directory); |
| | 418 | $this->_directory=$path; |
| | 419 | $this->_timestamps=$this->generateTimestamps($path); |
| | 420 | } |
| | 421 | |
| | 422 | /** |
| | 423 | * @return boolean whether the subdirectories of the directory will also be checked. |
| | 424 | * It defaults to true. |
| | 425 | */ |
| | 426 | public function getRecursiveCheck() |
| | 427 | { |
| | 428 | return $this->_recursiveCheck; |
| | 429 | } |
| | 430 | |
| | 431 | /** |
| | 432 | * @param boolean whether the subdirectories of the directory will also be checked. |
| | 433 | */ |
| | 434 | public function setRecursiveCheck($value) |
| | 435 | { |
| | 436 | $this->_recursiveCheck=TPropertyValue::ensureBoolean($value); |
| | 437 | } |
| | 438 | |
| | 439 | /** |
| | 440 | * @return int the depth of the subdirectories to be checked. |
| | 441 | * It defaults to -1, meaning unlimited depth. |
| | 442 | */ |
| | 443 | public function getRecursiveLevel() |
| | 444 | { |
| | 445 | return $this->_recursiveLevel; |
| | 446 | } |
| | 447 | |
| | 448 | /** |
| | 449 | * Sets a value indicating the depth of the subdirectories to be checked. |
| | 450 | * This is meaningful only when {@link getRecursiveCheck RecursiveCheck} |
| | 451 | * is true. |
| | 452 | * @param int the depth of the subdirectories to be checked. |
| | 453 | * If the value is less than 0, it means unlimited depth. |
| | 454 | * If the value is 0, it means checking the files directly under the specified directory. |
| | 455 | */ |
| | 456 | public function setRecursiveLevel($value) |
| | 457 | { |
| | 458 | $this->_recursiveLevel=TPropertyValue::ensureInteger($value); |
| | 459 | } |
| | 460 | |
| | 461 | /** |
| | 462 | * Performs the actual dependency checking. |
| | 463 | * This method returns true if the directory is changed. |
| | 464 | * @return boolean whether the dependency is changed or not. |
| | 465 | */ |
| | 466 | protected function checkChanges() |
| | 467 | { |
| | 468 | return $this->generateTimestamps($this->_directory)!=$this->_timestamps; |
| | 469 | } |
| | 470 | |
| | 471 | /** |
| | 472 | * Checks to see if the file should be checked for dependency. |
| | 473 | * This method is invoked when dependency of the whole directory is being checked. |
| | 474 | * By default, it always returns true, meaning the file should be checked. |
| | 475 | * You may override this method to check only certain files. |
| | 476 | * @param string the name of the file that may be checked for dependency. |
| | 477 | * @return boolean whether this file should be checked. |
| | 478 | */ |
| | 479 | protected function validateFile($fileName) |
| | 480 | { |
| | 481 | return true; |
| | 482 | } |
| | 483 | |
| | 484 | /** |
| | 485 | * Checks to see if the specified subdirectory should be checked for dependency. |
| | 486 | * This method is invoked when dependency of the whole directory is being checked. |
| | 487 | * By default, it always returns true, meaning the subdirectory should be checked. |
| | 488 | * You may override this method to check only certain subdirectories. |
| | 489 | * @param string the name of the subdirectory that may be checked for dependency. |
| | 490 | * @return boolean whether this subdirectory should be checked. |
| | 491 | */ |
| | 492 | protected function validateDirectory($directory) |
| | 493 | { |
| | 494 | return true; |
| | 495 | } |
| | 496 | |
| | 497 | /** |
| | 498 | * Determines the last modification time for files under the directory. |
| | 499 | * This method may go recurisvely into subdirectories if |
| | 500 | * {@link setRecursiveCheck RecursiveCheck} is set true. |
| | 501 | * @param string the directory name |
| | 502 | * @param int level of the recursion |
| | 503 | * @return array list of file modification time indexed by the file path |
| | 504 | */ |
| | 505 | protected function generateTimestamps($directory,$level=0) |
| | 506 | { |
| | 507 | if(($dir=opendir($directory))===false) |
| | 508 | throw new TIOException('directorycachedependency_directory_invalid',$directory); |
| | 509 | $timestamps=array(); |
| | 510 | while(($file=readdir($dir))!==false) |
| | 511 | { |
| | 512 | $path=$directory.DIRECTORY_SEPARATOR.$file; |
| | 513 | if($file==='.' || $file==='..') |
| | 514 | continue; |
| | 515 | else if(is_dir($path)) |
| | 516 | { |
| | 517 | if(($this->_recursiveLevel<0 || $level<$this->_recursiveLevel) && $this->validateDirectory($path)) |
| | 518 | $timestamps=array_merge($this->generateTimestamps($path,$level+1)); |
| | 519 | } |
| | 520 | else if($this->validateFile($path)) |
| | 521 | $timestamps[$path]=filemtime($path); |
| | 522 | } |
| | 523 | closedir($dir); |
| | 524 | return $timestamps; |
| | 525 | } |
| | 526 | } |
| | 527 | |
| | 528 | |
| | 529 | /** |
| | 530 | * TGlobalStateCacheDependency class. |
| | 531 | * |
| | 532 | * TGlobalStateCacheDependency checks if a global state is changed or not. |
| | 533 | * If the global state is changed, the dependency is reported as changed. |
| | 534 | * To specify which global state this dependency should check with, |
| | 535 | * set {@link setStateName StateName} to the name of the global state. |
| | 536 | * |
| | 537 | * @author Qiang Xue <qiang.xue@gmail.com> |
| | 538 | * @version $Id $ |
| | 539 | * @package System.Caching |
| | 540 | * @since 3.1.0 |
| | 541 | */ |
| | 542 | class TGlobalStateCacheDependency extends TCacheDependency |
| | 543 | { |
| | 544 | private $_stateName; |
| | 545 | private $_stateValue; |
| | 546 | |
| | 547 | /** |
| | 548 | * Constructor. |
| | 549 | * @param string the name of the global state |
| | 550 | */ |
| | 551 | public function __construct($name) |
| | 552 | { |
| | 553 | $this->setStateName($name); |
| | 554 | } |
| | 555 | |
| | 556 | /** |
| | 557 | * @return string the name of the global state |
| | 558 | */ |
| | 559 | public function getStateName() |
| | 560 | { |
| | 561 | return $this->_stateName; |
| | 562 | } |
| | 563 | |
| | 564 | /** |
| | 565 | * @param string the name of the global state |
| | 566 | * @see TApplication::setGlobalState |
| | 567 | */ |
| | 568 | public function setStateName($value) |
| | 569 | { |
| | 570 | $this->_stateName=$value; |
| | 571 | $this->_stateValue=Prado::getApplication()->getGlobalState($value); |
| | 572 | } |
| | 573 | |
| | 574 | /** |
| | 575 | * Performs the actual dependency checking. |
| | 576 | * This method returns true if the specified global state is changed. |
| | 577 | * @return boolean whether the dependency is changed or not. |
| | 578 | */ |
| | 579 | protected function checkChanges() |
| | 580 | { |
| | 581 | return $this->_stateValue!==Prado::getApplication()->getGlobalState($value); |
| | 582 | } |
| | 583 | } |
| | 584 | |
| | 585 | |
| | 586 | /** |
| | 587 | * TChainedCacheDependency class. |
| | 588 | * |
| | 589 | * TChainedCacheDependency represents a list of cache dependency objects |
| | 590 | * and performs the dependency checking based on the checking results of |
| | 591 | * these objects. If any of them reports a dependency change, TChainedCacheDependency |
| | 592 | * will return true for the checking. |
| | 593 | * |
| | 594 | * To add dependencies to TChainedCacheDependency, use {@link getDependencies Dependencies} |
| | 595 | * which gives a {@link TCacheDependencyList} instance and can be used like an array |
| | 596 | * (see {@link TList} for more details}). |
| | 597 | * |
| | 598 | * @author Qiang Xue <qiang.xue@gmail.com> |
| | 599 | * @version $Id $ |
| | 600 | * @package System.Caching |
| | 601 | * @since 3.1.0 |
| | 602 | */ |
| | 603 | class TChainedCacheDependency extends TCacheDependency |
| | 604 | { |
| | 605 | private $_dependencies=null; |
| | 606 | |
| | 607 | /** |
| | 608 | * @return TCacheDependencyList list of dependency objects |
| | 609 | */ |
| | 610 | public function getDependencies() |
| | 611 | { |
| | 612 | if($this->_dependencies===null) |
| | 613 | $this->_dependencies=new TCacheDependencyList; |
| | 614 | return $this->_dependencies; |
| | 615 | } |
| | 616 | |
| | 617 | /** |
| | 618 | * Performs the actual dependency checking. |
| | 619 | * This method returns true if any of the dependency objects |
| | 620 | * reports a dependency change. |
| | 621 | * @return boolean whether the dependency is changed or not. |
| | 622 | */ |
| | 623 | protected function checkChanges() |
| | 624 | { |
| | 625 | if($this->_dependencies!==null) |
| | 626 | { |
| | 627 | foreach($this->_dependencies as $dependency) |
| | 628 | if($dependency->getHasChanged()) |
| | 629 | return true; |
| | 630 | } |
| | 631 | return false; |
| | 632 | } |
| | 633 | } |
| | 634 | |
| | 635 | |
| | 636 | /** |
| | 637 | * TCacheDependencyList class. |
| | 638 | * |
| | 639 | * TCacheDependencyList represents a list of cache dependency objects. |
| | 640 | * Only objects implementing {@link ICacheDependency} can be added into this list. |
| | 641 | * |
| | 642 | * TCacheDependencyList can be used like an array. See {@link TList} |
| | 643 | * for more details. |
| | 644 | * |
| | 645 | * @author Qiang Xue <qiang.xue@gmail.com> |
| | 646 | * @version $Id $ |
| | 647 | * @package System.Caching |
| | 648 | * @since 3.1.0 |
| | 649 | */ |
| | 650 | class TCacheDependencyList extends TList |
| | 651 | { |
| | 652 | /** |
| | 653 | * Inserts an item at the specified position. |
| | 654 | * This overrides the parent implementation by performing additional type checking |
| | 655 | * for each newly added item. |
| | 656 | * @param integer the speicified position. |
| | 657 | * @param mixed new item |
| | 658 | * @throws TInvalidDataTypeException if the item to be inserted is not a dependency instance |
| | 659 | */ |
| | 660 | public function insertAt($index,$item) |
| | 661 | { |
| | 662 | if($item instanceof ICacheDependency) |
| | 663 | parent::insertAt($index,$item); |
| | 664 | else |
| | 665 | throw new TInvalidDataTypeException('cachedependencylist_cachedependency_required'); |
| | 666 | } |
| | 667 | } |
| | 668 | |