protected function validateTransaction(PhabricatorLiskDAO $object, $type, array $xactions)
 {
     $errors = parent::validateTransaction($object, $type, $xactions);
     switch ($type) {
         case PhabricatorProjectTransaction::TYPE_NAME:
             $missing = $this->validateIsEmptyTextField($object->getName(), $xactions);
             if ($missing) {
                 $error = new PhabricatorApplicationTransactionValidationError($type, pht('Required'), pht('Project name is required.'), nonempty(last($xactions), null));
                 $error->setIsMissingFieldError(true);
                 $errors[] = $error;
             }
             if (!$xactions) {
                 break;
             }
             if ($this->getIsMilestone()) {
                 break;
             }
             $name = last($xactions)->getNewValue();
             if (!PhabricatorSlug::isValidProjectSlug($name)) {
                 $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Project names must contain at least one letter or number.'), last($xactions));
                 break;
             }
             $slug = PhabricatorSlug::normalizeProjectSlug($name);
             $slug_used_already = id(new PhabricatorProjectSlug())->loadOneWhere('slug = %s', $slug);
             if ($slug_used_already && $slug_used_already->getProjectPHID() != $object->getPHID()) {
                 $error = new PhabricatorApplicationTransactionValidationError($type, pht('Duplicate'), pht('Project name generates the same hashtag ("%s") as another ' . 'existing project. Choose a unique name.', '#' . $slug), nonempty(last($xactions), null));
                 $errors[] = $error;
             }
             break;
         case PhabricatorProjectTransaction::TYPE_SLUGS:
             if (!$xactions) {
                 break;
             }
             $slug_xaction = last($xactions);
             $new = $slug_xaction->getNewValue();
             $invalid = array();
             foreach ($new as $slug) {
                 if (!PhabricatorSlug::isValidProjectSlug($slug)) {
                     $invalid[] = $slug;
                 }
             }
             if ($invalid) {
                 $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Hashtags must contain at least one letter or number. %s ' . 'project hashtag(s) are invalid: %s.', phutil_count($invalid), implode(', ', $invalid)), $slug_xaction);
                 break;
             }
             $new = $this->normalizeSlugs($new);
             if ($new) {
                 $slugs_used_already = id(new PhabricatorProjectSlug())->loadAllWhere('slug IN (%Ls)', $new);
             } else {
                 // The project doesn't have any extra slugs.
                 $slugs_used_already = array();
             }
             $slugs_used_already = mgroup($slugs_used_already, 'getProjectPHID');
             foreach ($slugs_used_already as $project_phid => $used_slugs) {
                 if ($project_phid == $object->getPHID()) {
                     continue;
                 }
                 $used_slug_strs = mpull($used_slugs, 'getSlug');
                 $error = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('%s project hashtag(s) are already used by other projects: %s.', phutil_count($used_slug_strs), implode(', ', $used_slug_strs)), $slug_xaction);
                 $errors[] = $error;
             }
             break;
         case PhabricatorProjectTransaction::TYPE_PARENT:
         case PhabricatorProjectTransaction::TYPE_MILESTONE:
             if (!$xactions) {
                 break;
             }
             $xaction = last($xactions);
             $parent_phid = $xaction->getNewValue();
             if (!$parent_phid) {
                 continue;
             }
             if (!$this->getIsNewObject()) {
                 $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('You can only set a parent or milestone project when creating a ' . 'project for the first time.'), $xaction);
                 break;
             }
             $projects = id(new PhabricatorProjectQuery())->setViewer($this->requireActor())->withPHIDs(array($parent_phid))->requireCapabilities(array(PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT))->execute();
             if (!$projects) {
                 $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Parent or milestone project PHID ("%s") must be the PHID of a ' . 'valid, visible project which you have permission to edit.', $parent_phid), $xaction);
                 break;
             }
             $project = head($projects);
             if ($project->isMilestone()) {
                 $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('Parent or milestone project PHID ("%s") must not be a ' . 'milestone. Milestones may not have subprojects or milestones.', $parent_phid), $xaction);
                 break;
             }
             $limit = PhabricatorProject::getProjectDepthLimit();
             if ($project->getProjectDepth() >= $limit - 1) {
                 $errors[] = new PhabricatorApplicationTransactionValidationError($type, pht('Invalid'), pht('You can not create a subproject or mielstone under this parent ' . 'because it would nest projects too deeply. The maximum ' . 'nesting depth of projects is %s.', new PhutilNumber($limit)), $xaction);
                 break;
             }
             $object->attachParentProject($project);
             break;
     }
     return $errors;
 }