PHPOffice/PHPWord · error · PhpOffice\PhpWord\Exception\Exception
Can not clone row, template variable not found or variable…
Error message
Can not clone row, template variable not found or variable contains markup.
What it means
TemplateProcessor::cloneRow searches the main document part for the completed macro '${search}'; if strpos returns false (or 0-falsy) it throws 'Can not clone row, template variable not found or variable contains markup.' — meaning the variable text is absent, split across XML runs (markup inside it), or the search did not match.
Solutions
- Retype the placeholder in Word as plain text (paste into Notepad first, then retype in the cell) to keep it in a single XML run
- Call $processor->cloneRow('variable', $n) with the bare variable name — ensureMacroCompleted adds ${}
- Verify the placeholder exists in the main body (not header/footer) by checking the template XML
- Ensure the template variable is in a table row (cloneRow is for table rows only)
- Use setValue before inspecting, or inspect the saved XML to confirm the exact placeholder text
Example fix
// before
$processor->cloneRow('${rowId}', 3); // wrong: no ${} needed, may not match
// after
$processor->cloneRow('rowId', 3);
$processor->setValue('rowId#1', 'first'); Defensive patterns
Strategy: try-catch
Validate before calling
$snippet = file_get_contents('template.docx'); // or inspect raw main part
// verify the placeholder survives as a contiguous string in the XML:
$hasMacro = strpos($mainPartXml, '${rowId}') !== false;
if (!$hasMacro) {
throw new RuntimeException('Placeholder ${rowId} split across XML runs or missing');
} Try / catch
try {
$template->cloneRow('rowId', 3);
} catch (\PhpOffice\PhpWord\Exception\Exception $e) {
if (str_contains($e->getMessage(), 'Can not clone row')) {
// retype placeholder in Word as plain text and re-check
}
} Prevention
- Pass the bare variable name to cloneRow (no ${}); it adds the macro itself
- Type placeholders as plain text; avoid editing mid-placeholder in Word
- Keep table-row placeholders in the document body, not headers/footers
- Diff the raw document.xml to confirm the placeholder is one contiguous string
When it happens
Trigger: cloneRow('variable', n) where '${variable}' is not in the template, or Word split the placeholder into multiple <w:t> runs due to spell-check/formatting so the contiguous string does not exist in the raw XML; also triggers when the placeholder is at position 0 handled incorrectly or in headers/footers (cloneRow only searches the main part).
Common situations: Placeholders typed into Word where autocorrect/spellcheck breaks them into separate XML runs; variable placed in a table in a header instead of the body; typo in variable name; missing ${...} delimiters in the search argument.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- Could not load the given XML document.
- The part " " doesn't exist
- Could not transform the given XML document.
- Could not set values for the given XSL style sheet…
- Invalid image
AI-assisted analysis of PHPOffice/PHPWord@aef95c0415 (2026-09-14).
Data as JSON: /api/errors/a16240bc07a2a807.
Report an issue: GitHub.
Appendix: source
Thrown at src/PhpWord/TemplateProcessor.php:768
*/
public function getVariables()
{
return array_keys($this->getVariableCount());
}
/**
* Clone a table row in a template document.
*
* @param string $search
* @param int $numberOfClones
*/
public function cloneRow($search, $numberOfClones): void
{
$search = static::ensureMacroCompleted($search);
$tagPos = strpos($this->tempDocumentMainPart, $search);
if (!$tagPos) {
throw new Exception('Can not clone row, template variable not found or variable contains markup.');
}
$rowStart = $this->findRowStart($tagPos);
$rowEnd = $this->findRowEnd($tagPos);
$xmlRow = $this->getSlice($rowStart, $rowEnd);
// Check if there's a cell spanning multiple rows.
if (preg_match('#<w:vMerge w:val="restart"/>#', $xmlRow)) {
// $extraRowStart = $rowEnd;
$extraRowEnd = $rowEnd;
while (true) {
$extraRowStart = $this->findRowStart($extraRowEnd + 1);
$extraRowEnd = $this->findRowEnd($extraRowEnd + 1);
// If extraRowEnd is lower then 7, there was no next row found.
if ($extraRowEnd < 7) {
break;
}View on GitHub (pinned to aef95c0415)