_getCache()->getMetadatas(self::CACHE_ID); return $meta ? date('Y-m-d H:i:s', $meta['mtime']) : null; } public function getList() { $cache = $this->_getCache(); if (false == ($report = $cache->load(self::CACHE_ID))) { $report = $this->_build(); $this->_getCache()->save($report, self::CACHE_ID); } return $report; } public function rebuild() { $report = $this->_build(); $this->_getCache()->save($report, self::CACHE_ID); return $this; } protected function _build() { $report = array(); $excludeFields = $this->getConfigArray('excludeFields', []); $fileExcludeFields = array_merge($excludeFields, array('url', 'link')); $urlExcludeFields = $excludeFields; $fileExcludePattern = '/' . implode('|', $fileExcludeFields) . '/i'; $urlExcludePattern = '/' . implode('|', $urlExcludeFields) . '|[a-z]+?Id/i'; $fileIncludePattern = '/file|image|path/i'; $urlIncludePattern = '/url|link|content|description/i'; $classes = $this->_filterClasses($this->_getClasses()); foreach ($classes as $info) { $fieldResult = array(); $classUrlFields = empty($info['urlParseFields']) ? array() : $info['urlParseFields']; $classFileFields = empty($info['fileFields']) ? array() : $info['fileFields']; $meta = $this->_getTableMeta($info['tableAlias']); foreach ($meta as $colInfo) { $name = $colInfo['COLUMN_NAME']; $type = strtolower($colInfo['DATA_TYPE']); $length = $colInfo['LENGTH']; // Undeclared File Fields if (in_array($type, array('char', 'varchar')) && $length > 12 && !in_array($name, $classFileFields)) { $exclude = preg_match($fileExcludePattern, $name); $include = preg_match($fileIncludePattern, $name); if ($include && !$exclude) { $importance = ($include) ? self::PRIORITY_HIGH : self::PRIORITY_LOW; $fieldResult[$name][self::ERR_UNDECLARED_FILE_FIELD] = $importance; } } // Undeclared Url Fields if (((in_array($type, array('char', 'varchar')) && $length > 12) || in_array($type, array('text', 'mediumtext', 'longtext'))) && !in_array($name, $classUrlFields) ) { $exclude = preg_match($urlExcludePattern, $name); $include = preg_match($urlIncludePattern, $name); if ($include && !$exclude) { $importance = ($include) ? self::PRIORITY_HIGH : self::PRIORITY_LOW; $fieldResult[$name][self::ERR_UNDECLARED_URL_FIELD] = $importance; } } if (isset($fieldResult[$name])) { $fieldResult[$name]['type'] = $type; } } // Non Existent File Fields if (($fileFieldsBroken = array_diff($classFileFields, array_keys($meta)))) { foreach ($fileFieldsBroken as $name) { $fieldResult[$name][self::ERR_NONEXISTENT_FILE_FIELD] = self::PRIORITY_HIGH; } } // Non Existent Url Fields if (($urlFieldsBroken = array_diff($classUrlFields, array_keys($meta)))) { foreach ($urlFieldsBroken as $name) { $fieldResult[$name][self::ERR_NONEXISTENT_URL_FIELD] = self::PRIORITY_HIGH; } } if ($fieldResult) { $report[] = array( 'className' => $info['className'], 'fileName' => str_replace(realpath(BASE_PATH . '/../') . '/', '', $info['fileName']), 'repositoryUrl' => $this->_getWebSvnUrl($info['fileName']), 'tableAlias' => $info['tableAlias'], 'fields' => $fieldResult, ); } } return $report; } protected function _getWebSvnUrl($fileName) { $needle = 'public_html'; if (false !== ($pos = strpos($fileName, $needle))) { $fileName = substr($fileName, $pos + strlen($needle) + 1); list ($repository, $path) = explode('/', $fileName, 2); $url = $this->getConfig('webSvnBaseUrl') . '/filedetails.php?' . http_build_query([ 'repname' => $repository, 'path' => '/' . $path ]); return $url; } return ''; } protected function _getCache() { if (null === $this->_cache) { $this->_cache = Qs_Cache::factory( 'Core', 'File', array( 'lifetime' => null, 'automatic_serialization' => true, ) ); } return $this->_cache; } protected function _filterClasses(array $classes) { $result = array(); foreach ($classes as $info) { if (!class_exists($info['className'], true)) { $this->_errors[] = 'Class "' . $info['className'] . '" does not exists'; continue; } $rf = new ReflectionClass($info['className']); if ($rf->isSubclassOf(self::DATA_OBJ_CLASS) && !$rf->isAbstract()) { $properties = $rf->getDefaultProperties(); $info['tableAlias'] = $properties['_tableAlias']; $info['fileFields'] = $properties['_fileFields']; $info['urlParseFields'] = $properties['_urlParseFields']; $result[$info['className']] = $info; } } return $result; } protected function _getTableMeta($tableAlias) { static $cache = array(); if ($tableAlias) { if (!isset($cache[$tableAlias])) { $cache[$tableAlias] = $this->_getTable($tableAlias)->getMetaData(); } return $cache[$tableAlias]; } return array(); } /** * Get file names with defined classes * @return array */ protected function _getClasses() { $path = BASE_PATH . '/App'; // out format: "./Gallery/Obj.php:class Obj" // "{relativePath}:{className}" $cmd = 'grep -REo -m 1 '; foreach ($this->getConfigArray('excludeDirs', []) as $dir) { $cmd .= '--exclude-dir="' . $dir . '" '; } $cmd .= '--include="*.php" "' . self::CLASS_PATTERN . '" ' . $path; exec($cmd, $out, $code); if (0 !== $code) { $this->_addError('Error executing command "' . $cmd . '"'); return false; } $classes = array(); foreach ($out as $line) { $line = explode(':', $line); $classes[] = array( 'className' => $this->_extractClass($line[0], $line[1]), 'fileName' => $line[0], ); } return $classes; } /** * @param string $path * @param string $line * @return bool|string */ protected function _extractClass($path, $line) { $namespace = false; $class = false; if (preg_match('/' . self::CLASS_PATTERN . '/i', $line, $match)) { $class = $match[1]; $namespace = $this->_getFileNamespace($path); } return ($namespace && $class) ? $namespace . '\\' . $class : $class; } /** * @param string $path * @return string|null|bool string - matches namespace, null - no namespace, false - error */ protected function _getFileNamespace($path) { if (false === ($h = fopen($path, 'r'))) { $this->_addError('Can not open file "' . $path . '"'); return false; } while (($line = fgets($h))) { if (false !== stripos($line, 'namespace ')) { if (preg_match('/' . self::NAMESPACE_PATTERN . '/i', $line, $matches)) { return $matches[1]; } return false; } if (false !== stripos($line, 'class ')) { return null; } } return null; } }